diff --git a/UI-DESIGN.md b/UI-DESIGN.md index e796f79a..ef2f40c0 100644 --- a/UI-DESIGN.md +++ b/UI-DESIGN.md @@ -772,7 +772,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证 ### 13.7 魔法笔记 -会话联动已接入:右侧提供可关闭的单实例笔记 Tab,左下角“快速笔记”打开同一面板,侧栏收起时由聊天页头提供入口。助手消息和会话标题菜单可打开文字采集预览;保存记录在面板和完整工作区显示独立来源条。草稿由 App 保留,关闭、替换及禁用前确认。具体交互由[魔法笔记会话联动界面设计](./docs/features/magic-notes/ui-design.md)维护。真实 Electron 验收已通过,全仓验证未全绿,见[验证进度](./docs/features/magic-notes/progress.md)。下列规则描述独立工作区。 +会话联动已接入:右侧提供可关闭的单实例笔记 Tab,通过工作栏“+ → 笔记”打开;左下角和聊天页头不再提供重复的“快速笔记”入口。助手消息和会话标题菜单可打开文字采集预览;保存记录在面板和完整工作区显示独立来源条。草稿由 App 保留,关闭、替换及禁用前确认。具体交互由[魔法笔记会话联动界面设计](./docs/features/magic-notes/ui-design.md)维护。真实 Electron 验收已通过,全仓验证未全绿,见[验证进度](./docs/features/magic-notes/progress.md)。下列规则描述独立工作区。 - 笔记总览与待办使用一个普通按钮切换,放在页头“新建笔记”按钮左边。按钮显示目标“切换到待办”或“切换到笔记”及对应图标,按内容宽度显示,不拉伸;窄窗口操作组可换行到标题下方,保留控件高度。 - 切换按钮不使用页签语义,切换后焦点回到同一个按钮;内容 section 持续提供当前列表的 `aria-label`。切换沿用草稿保护入口,待办状态仍使用独立的 `SegmentedControl`。 diff --git a/docs/features/magic-notes/logic-design.md b/docs/features/magic-notes/logic-design.md index 13d671d7..675f0cca 100644 --- a/docs/features/magic-notes/logic-design.md +++ b/docs/features/magic-notes/logic-design.md @@ -26,7 +26,7 @@ FR-6:成功写入触发现有通知刷新视图;面板重新挂载或工作 ## 启用与评论 -FR-1~FR-4:遵循魔法笔记应用的启用状态。禁用时隐藏会话采集、快速笔记及工作栏目录入口,并移除笔记 Tab;禁用前处理草稿。外部设置变更要求禁用时,选择继续编辑会尝试恢复启用设置,失败显示设置错误。应用未常驻不影响快速入口或工作栏可用性。设置继续由应用中心管理。 +FR-1~FR-4:遵循魔法笔记应用的启用状态。禁用时隐藏会话采集及工作栏目录入口,并移除笔记 Tab;禁用前处理草稿。外部设置变更要求禁用时,选择继续编辑会尝试恢复启用设置,失败显示设置错误。应用未常驻不影响会话采集或工作栏可用性。设置继续由应用中心管理。 会话采集和快速追加本身不调用模型。保存后沿用现有 AI 评论模式;只有已有设置要求保存后自动评论时才触发,失败不撤销保存,也不自动重新追加。 diff --git a/docs/features/magic-notes/prd.md b/docs/features/magic-notes/prd.md index 7d4431ed..b7d5d7fe 100644 --- a/docs/features/magic-notes/prd.md +++ b/docs/features/magic-notes/prd.md @@ -15,7 +15,7 @@ | FR-1 | 右侧助手工作栏增加“笔记”应用,支持搜索、打开已有笔记、新建笔记、查看记录摘要、快速追加文字,以及打开对应完整工作区。 | | FR-2 | 助手消息操作栏提供“加入笔记”,打开可编辑预览,用户选择已有笔记或新建后明确提交。 | | FR-3 | 会话标题的更多菜单提供“保存当前对话到笔记”,按触发时的会话内容生成预览,保存为目标笔记中的一条记录。 | -| FR-4 | 左侧底部提供“快速笔记”,侧栏收起时由聊天页头提供同名图标入口;打开右侧面板并恢复当前应用会话中最近选择的笔记,首次使用显示列表。此入口不隐式保存当前对话。 | +| FR-4 | 通过右侧工作栏“+ → 笔记”打开面板并恢复当前应用会话中最近选择的笔记,首次使用显示列表,不隐式保存当前对话。左侧底部和聊天页头不再提供重复的“快速笔记”入口。 | | FR-5 | 从会话保存的记录保留结构化来源,显示会话名称、所属项目(如有)和采集时间,支持返回原会话及单条来源消息。源会话删除后保留笔记内容。 | | FR-6 | 面板、完整工作区和 Agent 写入使用同一份笔记数据,成功写入后更新已打开视图,同时保护未保存草稿。 | diff --git a/docs/features/magic-notes/ui-design.md b/docs/features/magic-notes/ui-design.md index 76ad2e91..949536cb 100644 --- a/docs/features/magic-notes/ui-design.md +++ b/docs/features/magic-notes/ui-design.md @@ -9,16 +9,15 @@ | 助手工作栏“+”菜单 | 增加“笔记”,打开或激活单个笔记 Tab。 | | 助手消息底部,复制旁 | 图标按钮“加入笔记”,进入该消息的采集预览。 | | 会话标题更多菜单 | “保存当前对话到笔记”,加载完整历史后进入整段采集预览;生成、采集加载或保存期间禁用。 | -| 左侧底部,应用中心与设置操作组上方 | “快速笔记”,打开面板;侧栏收起时在聊天页头显示带同名提示的图标。 | | 面板笔记标题旁 | “在完整工作区打开”,进入当前笔记;有待保存文字时先处理草稿。 | -左侧主导航中的“魔法笔记”继续打开独立工作区,两处入口通过名称说明不同目的。快速笔记不读取文本选区,不根据选区改变动作。 +左侧主导航中的“魔法笔记”继续打开独立工作区。紧凑面板通过工作栏“+ → 笔记”打开,左侧底部和聊天页头不再提供重复的“快速笔记”入口。 ## 紧凑面板 面板按单列显示列表或笔记详情,不嵌入完整工作区的三栏布局。搜索位于列表顶部,“新建笔记”在列表之后;笔记行显示标题、置顶状态、两行摘要和更新时间。无笔记时提供新建动作;搜索无结果时保留搜索词并允许创建。 -详情顶部提供返回列表、笔记标题、全局范围标识和打开完整工作区。中间以可滚动记录摘要供浏览,每条文字摘要最多显示前 600 个字符,画布显示首页缩略图及页数;记录按钮可在完整工作区定位该记录。底部提供快速追加文字输入区与“加入笔记”按钮,画布编辑、待办管理和 AI 评论阅读仍在完整工作区。 +详情顶部提供返回列表、笔记标题和打开完整工作区,不显示“全局”标识。中间以可滚动记录摘要供浏览,每条文字摘要最多显示前 600 个字符,画布显示首页缩略图及页数;记录按钮可在完整工作区定位该记录。底部提供快速追加文字输入区与“加入笔记”按钮,画布编辑、待办管理和 AI 评论阅读仍在完整工作区。 从会话触发时,面板切到采集编辑视图: @@ -27,18 +26,19 @@ 来自:项目 / 会话标题 · 单条回答或当前对话 保存范围:用户与助手正文,不含推理、工具日志和附件文件 -待加入内容 [可编辑文字预览] 保存到 [搜索或选择已有笔记] [新建笔记 → 标题输入] -[取消] [加入笔记] + [取消] [加入笔记] ``` 范围说明随单条或整段来源调整。预览初始包含完整待保存文字,可滚动编辑。新建目标标题默认取会话标题前 100 个字符,可修改;已有目标与新建目标使用共享 `SegmentedControl` 切换。采集内容为空或超限、缺少目标、新建标题为空或超限时就地提示,并禁用保存;普通新建允许创建空笔记。 +预览和快速追加输入框通过无障碍名称“待加入内容”标识,不再显示重复标签。提交区仅显示校验或保存错误,不显示“全局”。提交与来源跳转按钮组均居右,可在窄面板换行;来源没有项目名时省略项目及分隔符,不补写“全局”。 + 读取失败显示就地重试按钮;保存失败保留输入并在提交区显示错误,用户可再次点击“加入笔记”。成功通过应用通知反馈,切回目标笔记详情,滚动定位、聚焦并高亮新增记录约 2.4 秒。仅“保存后自动评论”模式发起后续分析,分析失败另行通知,已保存内容保留。 ## 来源与可访问性 diff --git a/docs/pr-articles/2026-09-26-unreleased/article.md b/docs/pr-articles/2026-09-26-unreleased/article.md index a8ad5861..fab7c60d 100644 --- a/docs/pr-articles/2026-09-26-unreleased/article.md +++ b/docs/pr-articles/2026-09-26-unreleased/article.md @@ -44,7 +44,7 @@ GoodBuddy 这一轮更新围绕工作记录的整理与使用展开:监督者 保存后的记录带有“来自会话”入口。单条回答可以返回对应消息;原会话或消息被删除后,已保存的笔记内容仍保留。每次采集新增一条记录,后续对话修改不会自动同步到笔记。 -“快速笔记”入口和工作栏“+ → 笔记”可以打开紧凑面板,在聊天旁搜索笔记标题与正文、浏览摘要、追加文字。切换项目或会话时草稿保留,关闭有未保存内容的面板前会提示;草稿仅在本次应用运行期间保留。画布、待办和完整编辑继续在魔法笔记工作区中使用。 +工作栏“+ → 笔记”可以打开紧凑面板,在聊天旁搜索笔记标题与正文、浏览摘要、追加文字。切换项目或会话时草稿保留,关闭有未保存内容的面板前会提示;草稿仅在本次应用运行期间保留。画布、待办和完整编辑继续在魔法笔记工作区中使用。 采集保存文本与 Markdown,不复制推理过程、工具日志或附件图片。生成中的回答暂不可采集,单条记录上限为 500 KiB,超限会提示缩减。采集本身无需调用模型;保存后自动评论仍按已有设置执行。笔记继续属于全局,来源项目只用于帮助回看。 diff --git a/docs/pr-articles/2026-09-26-unreleased/review.md b/docs/pr-articles/2026-09-26-unreleased/review.md index 0e0fa2fd..48cc420b 100644 --- a/docs/pr-articles/2026-09-26-unreleased/review.md +++ b/docs/pr-articles/2026-09-26-unreleased/review.md @@ -33,7 +33,7 @@ | --- | --- | --- | --- | | 本机 Obsidian 接入 | 新增/设置→能力与工具→MCP | 注册仓库发现、指定文件夹、独立测试与保存、开关和 Runtime 分配;搜索、读取、写入、追加、局部修改等。默认关闭;直连模型及受管理本机 OpenCode/Continue 支持,DSH 与远程不支持;Ask 只读、Execute 可写;不是知识库复制或同步 | `55efca3`;`src/main/obsidian/obsidian-service.ts`、`src/shared/obsidian-tools.ts`、[Obsidian 进度](../../features/obsidian/progress.md) | | 聊天采集到魔法笔记 | 新增/助手消息、会话菜单 | 单条助手正文或完整会话用户/助手正文,预览编辑、追加或原子创建笔记;保留来源回跳,源删除后内容保留;不复制工具日志/附件,单记录 500 KiB,生成中不可采集 | `1094433`;`App.tsx`、`ChatTimeline.tsx`、`magic-notes-contracts.ts`、[笔记进度](../../features/magic-notes/progress.md) | -| 快速笔记与草稿 | 新增/快速笔记、工作栏“+” | 标题/正文搜索、摘要、文字追加;切项目/会话保留草稿,关闭/替换/禁用有确认,重启不恢复;仍是全局笔记,完整画布/待办编辑在工作区;采集本身不调模型,自动评论沿用设置 | `1094433`;`MagicNotesPanel.tsx`、`MagicNoteSource.tsx`、`use-magic-note-draft.tsx` | +| 快速笔记与草稿 | 工作栏“+ → 笔记” | 标题/正文搜索、摘要、文字追加;切项目/会话保留草稿,关闭/替换/禁用有确认,重启不恢复;仍是全局笔记,完整画布/待办编辑在工作区;采集本身不调模型,自动评论沿用设置 | `1094433`;`MagicNotesPanel.tsx`、`MagicNoteSource.tsx`、`use-magic-note-draft.tsx` | | 来源存储升级 | 兼容性/启动 | schema 48 保存来源元数据,不改写文件化正文;旧客户端不能直接打开升级数据库 | `1094433`;`assistant-database.ts`、笔记技术设计 | | 本地原生客户端 | 新增/聊天输入区 | Continue/OpenCode 工作栏终端、DS 系统浏览器 Web;沿用项目与模型,独立历史;DS 可复用、重开、停止,关闭网页不停止服务。远程协调入口未接通;Continue/DS 需标准 Node,托管准备可能联网;Continue/OpenCode 原生 Ask 暂无 MCP | `f81f342`;`native-client-coordinator.ts`、`native-terminal-client.ts`、`native-dsh-web-client.ts`、[客户端方案](../../features/assistant-workbar/runtime-native-client-proposal.md) | | DSH 依赖升级 | 新功能配套/内部版本 | DSH 0.1.7-rc.2、Cordis 4.0.4;不把依赖版本当模型名或稳定版承诺,正文并入原生客户端 | `f81f342`;提交中的 package/锁文件 | diff --git a/src/renderer/src/App.test.tsx b/src/renderer/src/App.test.tsx index 8759d241..112db866 100644 --- a/src/renderer/src/App.test.tsx +++ b/src/renderer/src/App.test.tsx @@ -14056,11 +14056,13 @@ describe("App", () => { const preview = await screen.findByLabelText('待加入内容') expect(preview).toHaveValue(messages.map(message => `## ${message.role === 'user' ? '用户' : '助手'}\n\n${message.content}`).join('\n\n')) expect(screen.getAllByRole('tab', { name: '笔记' })).toHaveLength(1) - fireEvent.click(screen.getByRole('button', { name: '快速笔记' })) + expect(screen.queryByRole('button', { name: '快速笔记' })).not.toBeInTheDocument() + fireEvent.click(screen.getByRole('button', { name: '打开工作栏应用' })) + fireEvent.click(screen.getByRole('button', { name: /^笔记搜索/ })) expect(screen.getAllByRole('tab', { name: '笔记' })).toHaveLength(1) fireEvent.change(preview, { target: { value: 'Frozen edited capture' } }) fireEvent.click(screen.getByRole('button', { name: /收起工作栏|助手工作栏/ })) - fireEvent.click(screen.getByRole('button', { name: '快速笔记' })) + fireEvent.click(screen.getByRole('button', { name: '切换助手工作栏' })) expect(await screen.findByLabelText('待加入内容')).toHaveValue('Frozen edited capture') fireEvent.click(screen.getByRole('button', { name: /新建对话/ })) expect(await screen.findByLabelText('待加入内容')).toHaveValue('Frozen edited capture') @@ -14150,7 +14152,9 @@ describe("App", () => { render() await screen.findByText('Source history 101') expect(screen.queryByText('Source history 1')).not.toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: '快速笔记' })) + fireEvent.click(screen.getByRole('button', { name: '切换助手工作栏' })) + fireEvent.click(screen.getByRole('button', { name: '打开工作栏应用' })) + fireEvent.click(screen.getByRole('button', { name: /^笔记搜索/ })) const notesPanel = within(await screen.findByRole('region', { name: '快速笔记' })) fireEvent.click(await notesPanel.findByRole('button', { name: /Source note/ })) fireEvent.click(await notesPanel.findByRole('button', { name: '查看来源消息' })) @@ -14172,7 +14176,9 @@ describe("App", () => { it('guards disabling Magic Notes with an unsaved quick draft before saving application settings', async () => { await api.updates!.updateSettings({ magicNotesEnabled: true }) render() - fireEvent.click(await screen.findByRole('button', { name: '快速笔记' })) + fireEvent.click(await screen.findByRole('button', { name: '切换助手工作栏' })) + fireEvent.click(screen.getByRole('button', { name: '打开工作栏应用' })) + fireEvent.click(screen.getByRole('button', { name: /^笔记搜索/ })) fireEvent.click(await screen.findByRole('button', { name: '新建笔记' })) fireEvent.change(screen.getByLabelText('笔记标题'), { target: { value: 'Unsaved quick note' } }) fireEvent.click(screen.getByRole('button', { name: '应用中心' })) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 3aeedab2..46eb6a3f 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -9340,7 +9340,6 @@ function App(): React.JSX.Element { ) : null} - {magicNotesEnabled && }
} {source.kind === 'message' && } diff --git a/src/renderer/src/MagicNotesPanel.test.tsx b/src/renderer/src/MagicNotesPanel.test.tsx index 70ce55c8..c38ba6d2 100644 --- a/src/renderer/src/MagicNotesPanel.test.tsx +++ b/src/renderer/src/MagicNotesPanel.test.tsx @@ -51,9 +51,13 @@ describe('compact notes', () => { render() fireEvent.click(screen.getByText('Capture')) await screen.findByDisplayValue('Captured text') + expect(screen.getByRole('textbox', { name: 'Text to add' })).toBeVisible() + expect(screen.queryByText('Text to add')).not.toBeInTheDocument() + expect(screen.queryByText('Global')).not.toBeInTheDocument() expect(create).not.toHaveBeenCalled() fireEvent.click(screen.getByRole('button', { name: 'New note' })) expect(screen.getByLabelText('Note title')).toHaveValue('Discussion A') + expect(document.getElementById('compact-note-validation')).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: 'Add to note' })) await waitFor(() => { expect(create).toHaveBeenCalledWith({ title: 'Discussion A', content: capturedNoteContent('Captured text'), source }) diff --git a/src/renderer/src/MagicNotesPanel.tsx b/src/renderer/src/MagicNotesPanel.tsx index b40eb97f..0b7d3a00 100644 --- a/src/renderer/src/MagicNotesPanel.tsx +++ b/src/renderer/src/MagicNotesPanel.tsx @@ -6,7 +6,7 @@ import type { MagicNoteCommentMode } from '../../shared/application-settings-con import { MagicNoteSource, type OpenMagicNoteSource } from './MagicNoteSource' import type { useMagicNoteDraft } from './use-magic-note-draft' import type { AppNotificationInput } from './notifications' -import { ScopeBadge, SegmentedControl } from './WorkspacePrimitives' +import { SegmentedControl } from './WorkspacePrimitives' import './magic-notes-panel.css' const MagicCanvasThumbnail = lazy(async () => ({ default: (await import('./MagicCanvasThumbnail')).MagicCanvasThumbnail })) @@ -149,7 +149,8 @@ export function MagicNotesPanel({ state, active, commentMode, commentFormat = 'c setSelectedNoteId('') requestAnimationFrame(() => document.getElementById('compact-note-search')?.focus()) } - const validationId = `compact-note-validation${saveError ? ' compact-note-save-error' : ''}` + const validationMessage = tooLong ? t('capture.tooLong') : draft && !valid ? t('capture.required') : undefined + const validationId = [validationMessage && 'compact-note-validation', saveError && 'compact-note-save-error'].filter(Boolean).join(' ') || undefined const list = ( <> @@ -182,7 +183,6 @@ export function MagicNotesPanel({ state, active, commentMode, commentFormat = 'c }}>
{draft?.source ? t('capture.add') : currentDetail?.title ?? t('page.title')} - {(!draft || quickDraft) && currentDetail && (
} - -