diff --git a/desktop/src/__tests__/memorySettings.test.tsx b/desktop/src/__tests__/memorySettings.test.tsx index de7619c8..6166ede1 100644 --- a/desktop/src/__tests__/memorySettings.test.tsx +++ b/desktop/src/__tests__/memorySettings.test.tsx @@ -112,6 +112,8 @@ describe('MemorySettings', () => { expect(memoryApiMock.listProjects).toHaveBeenCalledWith('/workspace/demo') expect(await screen.findByText('/workspace/demo')).toBeInTheDocument() expect(await screen.findByText('Project conventions.')).toBeInTheDocument() + expect(screen.queryByPlaceholderText('MEMORY.md or notes/project.md')).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: /create memory file/i })).not.toBeInTheDocument() const editor = await screen.findByLabelText('Editor') expect(editor).toHaveValue('# Project Memory\n') @@ -133,46 +135,6 @@ describe('MemorySettings', () => { }) }) - it('creates a new markdown memory file with a project frontmatter template', async () => { - memoryApiMock.listFiles - .mockResolvedValueOnce({ files: [] }) - .mockResolvedValueOnce({ - files: [ - { - path: 'notes/team.md', - name: 'team.md', - title: 'team', - bytes: 75, - updatedAt: '2026-05-01T00:02:00.000Z', - type: 'project', - isIndex: false, - }, - ], - }) - memoryApiMock.readFile.mockResolvedValueOnce({ - file: { - path: 'notes/team.md', - content: '# team\n', - updatedAt: '2026-05-01T00:02:00.000Z', - bytes: 75, - }, - }) - - render() - - const pathInput = await screen.findByPlaceholderText('MEMORY.md or notes/project.md') - fireEvent.change(pathInput, { target: { value: 'notes/team.md' } }) - fireEvent.click(screen.getByRole('button', { name: /create memory file/i })) - - await waitFor(() => { - expect(memoryApiMock.saveFile).toHaveBeenCalledWith({ - projectId: '-workspace-demo', - path: 'notes/team.md', - content: expect.stringContaining('type: project'), - }) - }) - }) - it('filters projects by path so large memory lists are navigable', async () => { memoryApiMock.listProjects.mockResolvedValue({ projects: [ diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts index 705d3c3f..bcac534c 100644 --- a/desktop/src/i18n/locales/en.ts +++ b/desktop/src/i18n/locales/en.ts @@ -549,11 +549,9 @@ export const en = { 'settings.memory.files': 'Memory files', 'settings.memory.editor': 'Editor', 'settings.memory.preview': 'Preview', - 'settings.memory.newPathPlaceholder': 'MEMORY.md or notes/project.md', - 'settings.memory.newFile': 'New', 'settings.memory.emptyProjects': 'No memory projects found.', - 'settings.memory.emptyFiles': 'No memory files yet. Create MEMORY.md to start.', - 'settings.memory.selectFile': 'Select or create a memory file.', + 'settings.memory.emptyFiles': 'No memory files found yet.', + 'settings.memory.selectFile': 'Select a memory file.', 'settings.memory.selectProject': 'Select a project.', 'settings.memory.noFileSelected': 'No file selected', 'settings.memory.current': 'Current', @@ -561,13 +559,10 @@ export const en = { 'settings.memory.unsaved': 'Unsaved', 'settings.memory.saved': 'Saved', 'settings.memory.revert': 'Revert', - 'settings.memory.invalidPath': 'Use a relative .md path without .. segments.', - 'settings.memory.fileExists': 'A memory file already exists at that path.', 'settings.memory.projectSearchPlaceholder': 'Search projects by path...', 'settings.memory.fileSearchPlaceholder': 'Search memory files...', 'settings.memory.noProjectMatches': 'No projects match this search.', 'settings.memory.noFileMatches': 'No memory files match this search.', - 'settings.memory.createMemoryFile': 'Create memory file', 'settings.memory.clearSearch': 'Clear search', // Settings > Plugins diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts index 43764257..4108c6dd 100644 --- a/desktop/src/i18n/locales/zh.ts +++ b/desktop/src/i18n/locales/zh.ts @@ -551,11 +551,9 @@ export const zh: Record = { 'settings.memory.files': '记忆文件', 'settings.memory.editor': '编辑', 'settings.memory.preview': '预览', - 'settings.memory.newPathPlaceholder': 'MEMORY.md 或 notes/project.md', - 'settings.memory.newFile': '新建', 'settings.memory.emptyProjects': '还没有找到记忆项目。', - 'settings.memory.emptyFiles': '还没有记忆文件。可以先创建 MEMORY.md。', - 'settings.memory.selectFile': '选择或创建一个记忆文件。', + 'settings.memory.emptyFiles': '还没有找到记忆文件。', + 'settings.memory.selectFile': '选择一个记忆文件。', 'settings.memory.selectProject': '选择一个项目。', 'settings.memory.noFileSelected': '未选择文件', 'settings.memory.current': '当前', @@ -563,13 +561,10 @@ export const zh: Record = { 'settings.memory.unsaved': '未保存', 'settings.memory.saved': '已保存', 'settings.memory.revert': '还原', - 'settings.memory.invalidPath': '请使用相对 .md 路径,且不能包含 .. 片段。', - 'settings.memory.fileExists': '该路径下已经有记忆文件。', 'settings.memory.projectSearchPlaceholder': '按路径搜索项目...', 'settings.memory.fileSearchPlaceholder': '搜索记忆文件...', 'settings.memory.noProjectMatches': '没有匹配的项目。', 'settings.memory.noFileMatches': '没有匹配的记忆文件。', - 'settings.memory.createMemoryFile': '创建记忆文件', 'settings.memory.clearSearch': '清空搜索', // Settings > Plugins diff --git a/desktop/src/pages/MemorySettings.tsx b/desktop/src/pages/MemorySettings.tsx index 2060cf68..9a5fa951 100644 --- a/desktop/src/pages/MemorySettings.tsx +++ b/desktop/src/pages/MemorySettings.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from 'react' -import { FileText, Plus, Search, X } from 'lucide-react' +import { FileText, Search, X } from 'lucide-react' import { Button } from '../components/shared/Button' import { MarkdownRenderer } from '../components/markdown/MarkdownRenderer' import { useTranslation } from '../i18n' @@ -31,14 +31,11 @@ export function MemorySettings() { openFile, updateDraft, saveFile, - createFile, } = useMemoryStore() const sessions = useSessionStore((s) => s.sessions) const activeSessionId = useSessionStore((s) => s.activeSessionId) const pendingMemoryPath = useUIStore((s) => s.pendingMemoryPath) const setPendingMemoryPath = useUIStore((s) => s.setPendingMemoryPath) - const [newPath, setNewPath] = useState('') - const [newPathError, setNewPathError] = useState(null) const [projectQuery, setProjectQuery] = useState('') const [fileQuery, setFileQuery] = useState('') @@ -130,22 +127,6 @@ export function MemorySettings() { void openFile(selectedProjectId, file.path) } - const handleCreate = () => { - if (!selectedProjectId) return - const path = normalizeMemoryPath(newPath || DEFAULT_MEMORY_PATH) - if (!isValidMemoryPath(path)) { - setNewPathError(t('settings.memory.invalidPath')) - return - } - if (files.some((file) => file.path === path)) { - setNewPathError(t('settings.memory.fileExists')) - return - } - setNewPath('') - setNewPathError(null) - void createFile(selectedProjectId, path, buildMemoryTemplate(path)) - } - return (
@@ -214,23 +195,8 @@ export function MemorySettings() { title={t('settings.memory.files')} meta={isLoadingFiles ? t('common.loading') : `${files.length}`} /> -
- { - setNewPath(value) - setNewPathError(null) - }} - onCreate={handleCreate} - disabled={!selectedProjectId} - loading={isSaving && !selectedFile} - placeholder={t('settings.memory.newPathPlaceholder')} - label={t('settings.memory.createMemoryFile')} - /> - {newPathError && ( -

{newPathError}

- )} - {files.length > 3 ? ( + {files.length > 3 ? ( +
- ) : null} -
+
+ ) : null}
{files.length === 0 && !isLoadingFiles ? ( @@ -373,48 +339,6 @@ function SearchField({ ) } -function CreateFileControl({ - value, - onChange, - onCreate, - disabled, - loading, - placeholder, - label, -}: { - value: string - onChange: (value: string) => void - onCreate: () => void - disabled: boolean - loading: boolean - placeholder: string - label: string -}) { - return ( -
- onChange(event.target.value)} - onKeyDown={(event) => { - if (event.key === 'Enter') onCreate() - }} - placeholder={placeholder} - aria-label={label} - className="h-11 min-w-0 rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] px-3 text-sm text-[var(--color-text-primary)] outline-none transition-colors duration-150 placeholder:text-[var(--color-text-tertiary)] focus:border-[var(--color-border-focus)] focus:shadow-[var(--shadow-focus-ring)]" - /> - -
- ) -} - function PanelHeader({ title, meta }: { title: string; meta: string }) { return (
@@ -510,10 +434,6 @@ function EmptyState({ text }: { text: string }) { ) } -function normalizeMemoryPath(value: string): string { - return value.trim().replace(/\\/g, '/').replace(/^\/+/, '') -} - function normalizeSearch(value: string): string { return value.toLowerCase().replace(/\\/g, '/').replace(/\/+/g, '/').trim() } @@ -571,28 +491,6 @@ function resolveMemoryFileTarget(projects: MemoryProject[], absolutePath: string return null } -function isValidMemoryPath(path: string): boolean { - return ( - path.length > 0 && - path.endsWith('.md') && - !path.includes('\0') && - !path.split('/').some((part) => part === '' || part === '.' || part === '..') - ) -} - -function buildMemoryTemplate(path: string): string { - const parts = path.split('/') - const title = parts[parts.length - 1]?.replace(/\.md$/, '') || 'Memory' - return `--- -type: project -description: Manually curated project memory. ---- - -# ${title} - -` -} - function formatDate(value: string): string { const date = new Date(value) if (Number.isNaN(date.getTime())) return ''