diff --git a/apps/web/tests/built-boot.expected.e2e.ts b/apps/web/tests/built-boot.expected.e2e.ts index 7ab01184fc..f645214133 100644 --- a/apps/web/tests/built-boot.expected.e2e.ts +++ b/apps/web/tests/built-boot.expected.e2e.ts @@ -129,7 +129,8 @@ it('boots the built plugin graph and renders a fixture session end to end', asyn const writeRow = mutationRows.find(row => row.getAttribute('data-variant') === 'write' && row.textContent?.includes('new-demo.txt')) if (writeRow === undefined) throw new Error('fixture write row missing') - expect(within(writeRow).getAllByText('+1 -0', { exact: true })).toHaveLength(1) + expect(within(writeRow).getAllByText('+1', { exact: true })).toHaveLength(1) + expect(within(writeRow).getAllByText('-0', { exact: true })).toHaveLength(1) expect(writeRow.querySelector('[data-diff]')).toBeNull() for (const row of mutationRows) { const toggle = row.querySelector('[data-expandable]') @@ -140,7 +141,8 @@ it('boots the built plugin graph and renders a fixture session end to end', asyn const writeDiff = writeRow.querySelector('[data-diff]') if (writeDiff === null) throw new Error('expanded fixture write diff missing') within(writeDiff).getByText('hello fixture', { exact: true }) - expect(within(writeRow).getAllByText('+1 -0', { exact: true })).toHaveLength(1) + expect(within(writeRow).getAllByText('+1', { exact: true })).toHaveLength(1) + expect(within(writeRow).getAllByText('-0', { exact: true })).toHaveLength(1) expect(writeDiff.textContent).not.toContain('+1 -0') // The web render intent reaches the assembled boot graph: the fixture's diff --git a/apps/web/tests/expected/clickable-links-gallery/ui.expected.md b/apps/web/tests/expected/clickable-links-gallery/ui.expected.md index f85e34a349..88f7cec6b8 100644 --- a/apps/web/tests/expected/clickable-links-gallery/ui.expected.md +++ b/apps/web/tests/expected/clickable-links-gallery/ui.expected.md @@ -7,8 +7,8 @@ - tab "Trajectory" - text: "Assemble the link gallery: write the report and styles, inspect the sources, and summarize. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Wrote files, called tools, searched code, etc." [expanded] - button "Write site/report.html +1 -0": - text: Write diff --git a/apps/web/tests/expected/cordis-history/ui.expected.md b/apps/web/tests/expected/cordis-history/ui.expected.md index 5337f00b50..cb66182d87 100644 --- a/apps/web/tests/expected/cordis-history/ui.expected.md +++ b/apps/web/tests/expected/cordis-history/ui.expected.md @@ -11,8 +11,8 @@ - button "Jump to turn 3" - text: "Use only Cordis tools. First call cordis_inspect_self with no arguments. Then call cordis_define with plugin kind \"new\", idPrefix \"snap\", name \"snapshot noop\", purpose \"does nothing, for the snapshot\", code.host exactly \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\" and code.client exactly \"return { inject: [\\\"slots\\\"], apply(ctx) { ctx.slots.register({ name: \\\"shell.overlay\\\", id: \\\"snapshot-probe\\\" }, () => React.createElement(\\\"div\\\", { \\\"data-snapshot-probe\\\": \\\"loaded\\\" })) } }\". Read its returned pluginId and packageId, then call cordis_run with those exact IDs and mode \"run\". After the run request returns, reply exactly CORDIS_UI_READY and stop. 9/1 {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Called tools" [expanded] - button "Think I will inspect the current Session's dynamic Cordis Plugins before defining the snapshot Package." - button "Inspect plugins 0 dynamic plugins" @@ -42,8 +42,8 @@ - button "Plugin status updated 9/1 {{clock}}": - text: Plugin status updated - time: 9/1 {{clock}} -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: The Cordis Plugin is running. - button "Copy" - button "Good response" @@ -51,8 +51,8 @@ - button "Branch into a new conversation" - text: 9/1 {{clock}} Use only Cordis tools. Call cordis_stop with pluginId "snap-1". After it succeeds, reply exactly CORDIS_UI_DONE and stop. 9/1 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Called tools" [expanded] - text: Stop Cordis Plugin snap-1 - button "Inspect" diff --git a/apps/web/tests/expected/desktop-locale/automatic.expected.md b/apps/web/tests/expected/desktop-locale/automatic.expected.md index a243bc300c..77b4593b90 100644 --- a/apps/web/tests/expected/desktop-locale/automatic.expected.md +++ b/apps/web/tests/expected/desktop-locale/automatic.expected.md @@ -20,8 +20,6 @@ - button "减小字号" - text: px 工作步骤展示 选择希望看到多少工具调用细节 - button "标准" - - text: 性能与用量 选择性能与用量信息展示的详细程度 - - button "详细" - text: 代码工作工具 开启后显示轨迹、本轮代码差异,新对话中的 Agent 预设切换 - switch "代码工作工具" [checked] - text: 快捷键 @@ -31,4 +29,6 @@ - button "排队发送" - text: 在使用官方模型 API 时上传 Session Log 帮助改进 DeepSeek 模型与产品 - switch "在使用官方模型 API 时上传 Session Log" + - text: 性能与用量 选择性能与用量信息展示的详细程度 + - button "详细" - text: 当前版本:{{version}} diff --git a/apps/web/tests/expected/desktop-locale/selected.expected.md b/apps/web/tests/expected/desktop-locale/selected.expected.md index 25e93094dd..c4fd5e5eff 100644 --- a/apps/web/tests/expected/desktop-locale/selected.expected.md +++ b/apps/web/tests/expected/desktop-locale/selected.expected.md @@ -20,8 +20,6 @@ - button "Decrease font size" - text: px Work details Choose how much detail to show for tool calls - button "Standard" - - text: Performance & usage Choose how much performance and usage information to show - - button "Detailed" - text: Coding Tools Shows trajectory, code diffs, and Agent preset switching in new chats - switch "Coding Tools" [checked] - text: Keyboard shortcuts @@ -31,4 +29,6 @@ - button "Queue" - text: Upload Session Log when using the official model API Help improve DeepSeek models and products. - switch "Upload Session Log when using the official model API" + - text: Performance & usage Choose how much performance and usage information to show + - button "Detailed" - text: "Current version: {{version}}" diff --git a/apps/web/tests/expected/fork-mid-turn/ui.expected.md b/apps/web/tests/expected/fork-mid-turn/ui.expected.md index 436c32ff6e..ba27240c6a 100644 --- a/apps/web/tests/expected/fork-mid-turn/ui.expected.md +++ b/apps/web/tests/expected/fork-mid-turn/ui.expected.md @@ -1,11 +1,11 @@ - text: "Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - text: FORK_BRANCH_USER Summarize what this branch knows about the reads. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: FORK_BRANCH_FIRST this branch has no read results, so I would check what the parent completed before retrying. FORK_BRANCH_DONE. - button "Copy" - button "Good response" diff --git a/apps/web/tests/expected/github-ready-review/conversation-expanded.expected.md b/apps/web/tests/expected/github-ready-review/conversation-expanded.expected.md index 5f580db1a6..4abdc53e16 100644 --- a/apps/web/tests/expected/github-ready-review/conversation-expanded.expected.md +++ b/apps/web/tests/expected/github-ready-review/conversation-expanded.expected.md @@ -19,8 +19,8 @@ - button "GitHub event received {{clock}}": - text: GitHub event received - time: {{clock}} -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: "Review complete: no actionable findings." - button "Copy" - button "Good response" diff --git a/apps/web/tests/expected/github-ready-review/conversation.expected.md b/apps/web/tests/expected/github-ready-review/conversation.expected.md index 5f580db1a6..4abdc53e16 100644 --- a/apps/web/tests/expected/github-ready-review/conversation.expected.md +++ b/apps/web/tests/expected/github-ready-review/conversation.expected.md @@ -19,8 +19,8 @@ - button "GitHub event received {{clock}}": - text: GitHub event received - time: {{clock}} -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: "Review complete: no actionable findings." - button "Copy" - button "Good response" diff --git a/apps/web/tests/expected/markdown-cjk-strong/ui.expected.md b/apps/web/tests/expected/markdown-cjk-strong/ui.expected.md index e113cb6b3d..2eda5d3afb 100644 --- a/apps/web/tests/expected/markdown-cjk-strong/ui.expected.md +++ b/apps/web/tests/expected/markdown-cjk-strong/ui.expected.md @@ -7,8 +7,8 @@ - tab "Trajectory" - text: Render adjacent CJK strong emphasis. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - heading "CJK strong emphasis" [level=2] - paragraph: - strong: 注意: diff --git a/apps/web/tests/expected/markdown-images/ui.expected.md b/apps/web/tests/expected/markdown-images/ui.expected.md index e830dad629..ccaf8069a3 100644 --- a/apps/web/tests/expected/markdown-images/ui.expected.md +++ b/apps/web/tests/expected/markdown-images/ui.expected.md @@ -7,8 +7,8 @@ - tab "Trajectory" - text: Show the Markdown image policy. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - heading "Markdown images" [level=2] - paragraph: - 'button "View full image: Remote test image"': diff --git a/apps/web/tests/expected/markdown-inline-code-links/ui.expected.md b/apps/web/tests/expected/markdown-inline-code-links/ui.expected.md index bf25c8d4ae..c9d932b478 100644 --- a/apps/web/tests/expected/markdown-inline-code-links/ui.expected.md +++ b/apps/web/tests/expected/markdown-inline-code-links/ui.expected.md @@ -6,8 +6,8 @@ - tab "Trajectory" - text: Show the local preview URL. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - heading "Inline code links" [level=2] - paragraph: - text: "Preview:" diff --git a/apps/web/tests/expected/math-rendering/ui.expected.md b/apps/web/tests/expected/math-rendering/ui.expected.md index 3fb277318a..3d4b08ccd1 100644 --- a/apps/web/tests/expected/math-rendering/ui.expected.md +++ b/apps/web/tests/expected/math-rendering/ui.expected.md @@ -7,8 +7,8 @@ - tab "Trajectory" - text: Render this mathematical proof. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - heading "Math rendering" [level=2] - paragraph: - text: Inline dollar diff --git a/apps/web/tests/expected/reference-composer/order.expected.md b/apps/web/tests/expected/reference-composer/order.expected.md index 589d2954d1..779c3e1f20 100644 --- a/apps/web/tests/expected/reference-composer/order.expected.md +++ b/apps/web/tests/expected/reference-composer/order.expected.md @@ -7,8 +7,8 @@ - tab "Trajectory" - text: Research notes what changed? Referenced session · Research notes {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - textbox "Message or run a task, / commands, @ files or sessions" - button "Add files or run commands" - 'button "Access mode, current: Workspace Write"': Workspace Write diff --git a/apps/web/tests/expected/settings-chrome/dialog-en.expected.md b/apps/web/tests/expected/settings-chrome/dialog-en.expected.md index bebeced11e..ad3348ea5a 100644 --- a/apps/web/tests/expected/settings-chrome/dialog-en.expected.md +++ b/apps/web/tests/expected/settings-chrome/dialog-en.expected.md @@ -20,8 +20,6 @@ - button "Decrease font size" - text: px Work details Choose how much detail to show for tool calls - button "Detailed" - - text: Performance & usage Choose how much performance and usage information to show - - button "Detailed" - text: Coding Tools Shows trajectory, code diffs, and Agent preset switching in new chats - switch "Coding Tools" - text: Keyboard shortcuts @@ -31,4 +29,6 @@ - button "Queue" - text: Upload Session Log when using the official model API Help improve DeepSeek models and products. - switch "Upload Session Log when using the official model API" + - text: Performance & usage Choose how much performance and usage information to show + - button "Detailed" - text: "Current version: {{version}}" diff --git a/apps/web/tests/expected/settings-chrome/dialog-no-browser.expected.md b/apps/web/tests/expected/settings-chrome/dialog-no-browser.expected.md index bebeced11e..ad3348ea5a 100644 --- a/apps/web/tests/expected/settings-chrome/dialog-no-browser.expected.md +++ b/apps/web/tests/expected/settings-chrome/dialog-no-browser.expected.md @@ -20,8 +20,6 @@ - button "Decrease font size" - text: px Work details Choose how much detail to show for tool calls - button "Detailed" - - text: Performance & usage Choose how much performance and usage information to show - - button "Detailed" - text: Coding Tools Shows trajectory, code diffs, and Agent preset switching in new chats - switch "Coding Tools" - text: Keyboard shortcuts @@ -31,4 +29,6 @@ - button "Queue" - text: Upload Session Log when using the official model API Help improve DeepSeek models and products. - switch "Upload Session Log when using the official model API" + - text: Performance & usage Choose how much performance and usage information to show + - button "Detailed" - text: "Current version: {{version}}" diff --git a/apps/web/tests/expected/settings-chrome/dialog.expected.md b/apps/web/tests/expected/settings-chrome/dialog.expected.md index 6bbb8835aa..0f17fa643f 100644 --- a/apps/web/tests/expected/settings-chrome/dialog.expected.md +++ b/apps/web/tests/expected/settings-chrome/dialog.expected.md @@ -20,8 +20,6 @@ - button "减小字号" - text: px 工作步骤展示 选择希望看到多少工具调用细节 - button "详细" - - text: 性能与用量 选择性能与用量信息展示的详细程度 - - button "详细" - text: 代码工作工具 开启后显示轨迹、本轮代码差异,新对话中的 Agent 预设切换 - switch "代码工作工具" - text: 快捷键 @@ -31,4 +29,6 @@ - button "排队发送" - text: 在使用官方模型 API 时上传 Session Log 帮助改进 DeepSeek 模型与产品 - switch "在使用官方模型 API 时上传 Session Log" + - text: 性能与用量 选择性能与用量信息展示的详细程度 + - button "详细" - text: 当前版本:{{version}} diff --git a/apps/web/tests/expected/shortcuts-workspace/macos-defaults.expected.md b/apps/web/tests/expected/shortcuts-workspace/macos-defaults.expected.md index ceed422820..8624180efa 100644 --- a/apps/web/tests/expected/shortcuts-workspace/macos-defaults.expected.md +++ b/apps/web/tests/expected/shortcuts-workspace/macos-defaults.expected.md @@ -23,7 +23,7 @@ - text: Add workspace ⌥ ⌘ O - listitem: - button "Edit shortcut for Rename session" - - text: Rename session ⇧ ⌘ R + - text: Rename session ⌥ ⌘ G - listitem: - button "Edit shortcut for Fork session" - text: Fork session ⇧ ⌘ F @@ -32,7 +32,7 @@ - text: Archive session ⌥ ⌘ A - listitem: - button "Edit shortcut for Open settings" - - text: Open settings ⌘ , + - text: Open settings ⌥ ⌘ , - listitem: - button "Edit shortcut for Toggle right sidebar" - text: Toggle right sidebar ⇧ ⌘ B diff --git a/apps/web/tests/expected/shortcuts/en-US.expected.md b/apps/web/tests/expected/shortcuts/en-US.expected.md index d3740de7a2..7701d61c67 100644 --- a/apps/web/tests/expected/shortcuts/en-US.expected.md +++ b/apps/web/tests/expected/shortcuts/en-US.expected.md @@ -23,7 +23,7 @@ - text: Add workspace Ctrl + Alt + O - listitem: - button "Edit shortcut for Rename session" - - text: Rename session Ctrl + Shift + R + - text: Rename session Ctrl + Alt + G - listitem: - button "Edit shortcut for Fork session" - text: Fork session Ctrl + Shift + F @@ -32,7 +32,7 @@ - text: Archive session Ctrl + Alt + A - listitem: - button "Edit shortcut for Open settings" - - text: Open settings Ctrl + , + - text: Open settings Ctrl + Alt + , - listitem: - button "Edit shortcut for Toggle right sidebar" - text: Toggle right sidebar Ctrl + Shift + B diff --git a/apps/web/tests/expected/shortcuts/zh-CN.expected.md b/apps/web/tests/expected/shortcuts/zh-CN.expected.md index ce19e0b876..e1fb9ccd2a 100644 --- a/apps/web/tests/expected/shortcuts/zh-CN.expected.md +++ b/apps/web/tests/expected/shortcuts/zh-CN.expected.md @@ -23,7 +23,7 @@ - text: 添加工作区 ⌥ ⌘ O - listitem: - button "修改重命名会话快捷键" - - text: 重命名会话 ⇧ ⌘ R + - text: 重命名会话 ⌥ ⌘ G - listitem: - button "修改分叉会话快捷键" - text: 分叉会话 ⇧ ⌘ F @@ -32,7 +32,7 @@ - text: 归档会话 ⌥ ⌘ A - listitem: - button "修改打开设置快捷键" - - text: 打开设置 ⌘ , + - text: 打开设置 ⌥ ⌘ , - listitem: - button "修改展开/收起右侧栏快捷键" - text: 展开/收起右侧栏 ⇧ ⌘ B diff --git a/apps/web/tests/expected/skill-user-invoke/ui-expanded.expected.md b/apps/web/tests/expected/skill-user-invoke/ui-expanded.expected.md index 930e9eb385..09a6526be6 100644 --- a/apps/web/tests/expected/skill-user-invoke/ui-expanded.expected.md +++ b/apps/web/tests/expected/skill-user-invoke/ui-expanded.expected.md @@ -10,8 +10,8 @@ - button "meeting notes-this-is-a-very-long-filename-for-testing-user-message-file-references-and-preview-layout.md" - text: and confirm the fixture wiring {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: USER_INVOKE_REPLY acknowledged; following the injected skill. - button "Copy" - button "Good response" diff --git a/apps/web/tests/expected/skill-user-invoke/ui.expected.md b/apps/web/tests/expected/skill-user-invoke/ui.expected.md index 930e9eb385..09a6526be6 100644 --- a/apps/web/tests/expected/skill-user-invoke/ui.expected.md +++ b/apps/web/tests/expected/skill-user-invoke/ui.expected.md @@ -10,8 +10,8 @@ - button "meeting notes-this-is-a-very-long-filename-for-testing-user-message-file-references-and-preview-layout.md" - text: and confirm the fixture wiring {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: USER_INVOKE_REPLY acknowledged; following the injected skill. - button "Copy" - button "Good response" diff --git a/apps/web/tests/expected/stats-paged-history/ui.expected.md b/apps/web/tests/expected/stats-paged-history/ui.expected.md index 5af4333df1..e6a96742a4 100644 --- a/apps/web/tests/expected/stats-paged-history/ui.expected.md +++ b/apps/web/tests/expected/stats-paged-history/ui.expected.md @@ -36,8 +36,8 @@ - button "Jump to turn 28" - text: m1 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r1 - button "Copy" - button "Good response" @@ -45,8 +45,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m2 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r2 - button "Copy" - button "Good response" @@ -54,8 +54,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m3 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r3 - button "Copy" - button "Good response" @@ -63,8 +63,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m4 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r4 - button "Copy" - button "Good response" @@ -72,8 +72,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m5 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r5 - button "Copy" - button "Good response" @@ -81,8 +81,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m6 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r6 - button "Copy" - button "Good response" @@ -90,8 +90,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m7 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r7 - button "Copy" - button "Good response" @@ -99,8 +99,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m8 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r8 - button "Copy" - button "Good response" @@ -108,8 +108,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m9 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r9 - button "Copy" - button "Good response" @@ -117,8 +117,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m10 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r10 - button "Copy" - button "Good response" @@ -126,8 +126,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m11 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r11 - button "Copy" - button "Good response" @@ -135,8 +135,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m12 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r12 - button "Copy" - button "Good response" @@ -144,8 +144,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m13 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r13 - button "Copy" - button "Good response" @@ -153,8 +153,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m14 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r14 - button "Copy" - button "Good response" @@ -162,8 +162,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m15 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r15 - button "Copy" - button "Good response" @@ -171,8 +171,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m16 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r16 - button "Copy" - button "Good response" @@ -180,8 +180,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m17 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r17 - button "Copy" - button "Good response" @@ -189,8 +189,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m18 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r18 - button "Copy" - button "Good response" @@ -198,8 +198,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m19 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r19 - button "Copy" - button "Good response" @@ -207,8 +207,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m20 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r20 - button "Copy" - button "Good response" @@ -216,8 +216,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m21 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r21 - button "Copy" - button "Good response" @@ -225,8 +225,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m22 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r22 - button "Copy" - button "Good response" @@ -234,8 +234,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m23 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r23 - button "Copy" - button "Good response" @@ -243,8 +243,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m24 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r24 - button "Copy" - button "Good response" @@ -252,8 +252,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m25 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r25 - button "Copy" - button "Good response" @@ -261,8 +261,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m26 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r26 - button "Copy" - button "Good response" @@ -270,8 +270,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m27 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r27 - button "Copy" - button "Good response" @@ -279,8 +279,8 @@ - button "Branch into a new conversation" - text: 7/25 {{clock}} m28 7/25 {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: r28 - button "Copy" - button "Good response" diff --git a/apps/web/tests/expected/steer-all/mid-steer.expected.md b/apps/web/tests/expected/steer-all/mid-steer.expected.md index d37563c909..57c5dc2565 100644 --- a/apps/web/tests/expected/steer-all/mid-steer.expected.md +++ b/apps/web/tests/expected/steer-all/mid-steer.expected.md @@ -13,8 +13,8 @@ - button "Copy" - text: "Interjection: include the word ORANGE in your final reply." - button "Copy" -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - textbox "Message or run a task, / commands, @ files or sessions" - button "Add files or run commands" - 'button "Access mode, current: Workspace Write"': Workspace Write diff --git a/apps/web/tests/expected/steer-all/settled-expanded.expected.md b/apps/web/tests/expected/steer-all/settled-expanded.expected.md index eb11ecdd4c..bcc7cac374 100644 --- a/apps/web/tests/expected/steer-all/settled-expanded.expected.md +++ b/apps/web/tests/expected/steer-all/settled-expanded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Use the ask_user_question tool to ask me exactly one question with id "checkpoint", question "Ready to continue?", header "Checkpoint", and options labeled "Yes" and "No". After I answer, reply with one short sentence acknowledging my answer and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - button "Asked questions" [expanded] - button "Think The user wants me to ask them a checkpoint question first, then continue with whatever they interject. Let me do exactly that." - button "Ask question 1/1 answered" diff --git a/apps/web/tests/expected/steer-all/settled.expected.md b/apps/web/tests/expected/steer-all/settled.expected.md index f419491430..c92baa21e1 100644 --- a/apps/web/tests/expected/steer-all/settled.expected.md +++ b/apps/web/tests/expected/steer-all/settled.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Use the ask_user_question tool to ask me exactly one question with id "checkpoint", question "Ready to continue?", header "Checkpoint", and options labeled "Yes" and "No". After I answer, reply with one short sentence acknowledging my answer and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - button "Asked questions" - text: "Interjection: include the word BANANA in your final reply. {{clock}}" - button "Copy" diff --git a/apps/web/tests/goal-multi-turn-actions.e2e.ts b/apps/web/tests/goal-multi-turn-actions.e2e.ts index 1361a476d3..4a1c6e8710 100644 --- a/apps/web/tests/goal-multi-turn-actions.e2e.ts +++ b/apps/web/tests/goal-multi-turn-actions.e2e.ts @@ -155,6 +155,32 @@ describe('web e2e: Goal keeps one assistant action row per completed turn', () = expect(sessionEvents.flatMap(event => event.type === 'request/header' ? [event.data.reason] : [])).toEqual(['initial', 'series']) await expect.poll(() => page.locator('[data-turn-process]').count(), { timeout: 15_000 }).toBe(2) + const trigger = page.locator('[data-turn-trigger]').first() + const process = page.locator('[data-turn-process]').first() + const originalFontSize = await page.evaluate(() => document.body.style.getPropertyValue('--dsh-content-font-size')) + await process.click() + try { + for (const fontSize of [10, 14, 22]) { + await scaffold!.ctx.settings.update('ui-theme', { fontSize }) + await expect.poll(() => page.evaluate(() => document.body.style.getPropertyValue('--dsh-content-font-size'))) + .toBe(`${String(fontSize)}px`) + const group = page.locator('[data-process-activity]:visible').first() + const groupFontSize = await group.evaluate(element => getComputedStyle(element).fontSize) + expect(groupFontSize).toBe(`${String(fontSize)}px`) + expect(await process.evaluate(element => getComputedStyle(element).fontSize)).toBe(groupFontSize) + expect(await trigger.getByText('Continuing goal', { exact: true }) + .evaluate(element => getComputedStyle(element).fontSize)).toBe(groupFontSize) + expect(await process.evaluate(element => element.getBoundingClientRect().top)) + .toBe(await trigger.evaluate(element => element.getBoundingClientRect().bottom) + 16) + expect(await group.evaluate(element => element.getBoundingClientRect().top)) + .toBe(await process.evaluate(element => element.getBoundingClientRect().bottom) + 16) + } + } finally { + await scaffold!.ctx.settings.update('ui-theme', { fontSize: Number.parseFloat(originalFontSize) }) + await expect.poll(() => page.evaluate(() => document.body.style.getPropertyValue('--dsh-content-font-size'))) + .toBe(originalFontSize) + await process.click() + } expect(await page.getByRole('button', { name: 'System prompt' }).count()).toBe(0) expect(await page.locator( '[data-chat-flow-kind="system-prompt"]', diff --git a/apps/web/tests/live-interactions.e2e.ts b/apps/web/tests/live-interactions.e2e.ts index f598a285e8..2af1c89b66 100644 --- a/apps/web/tests/live-interactions.e2e.ts +++ b/apps/web/tests/live-interactions.e2e.ts @@ -161,7 +161,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { () => page.getByRole('status').filter({ hasText: 'Deep diving' }).isVisible(), { timeout: 10_000 }, ).toBe(true) - await page.locator('[data-streaming="true"]') + await page.locator('[data-streaming="true"]:not([inert] *)') .getByText('partial', { exact: true }) .waitFor({ timeout: 30_000 }) const loadingSnapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold!.workspaceCwd) @@ -195,7 +195,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { // (awaited above), but the abort frame reaches the browser over SSE — the // frozen-partial swap is eventually consistent, so poll rather than count. await expect.poll(() => page.locator('[data-composer-input]').first().isEnabled(), { timeout: 10_000 }).toBe(true) - await expect.poll(() => page.locator('[data-streaming="true"]').count(), { timeout: 10_000 }).toBe(0) + await expect.poll(() => page.locator('[data-streaming="true"]:not([inert] *)').count(), { timeout: 10_000 }).toBe(0) await expect.poll(() => page.getByRole('tooltip').count()).toBe(0) // Golden of the aborted end-state: the prompt bubble plus the frozen // partial ('partial' is the hang entry's replayed prefix) and no more. @@ -222,7 +222,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { // AUTH is outside llm-retry's retryable set: no retry record. expect(sessionEvents.filter(e => e.type === 'llm/retry').length).toBe(0) await expect.poll(() => page.locator('[data-composer-input]').first().isEnabled(), { timeout: 10_000 }).toBe(true) - expect(await page.locator('[data-streaming="true"]').count()).toBe(0) + expect(await page.locator('[data-streaming="true"]:not([inert] *)').count()).toBe(0) const errorStatus = page.getByRole('status').filter({ hasText: 'This turn failed' }) await errorStatus.waitFor({ timeout: 10_000 }) expect(await errorStatus.textContent()).toContain('API key is invalid') @@ -292,6 +292,40 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { expect(tripwire.warnings).toEqual([]) }, 120_000) + it.skipIf(MODE === 'record')('wraps an active retry label inside a narrow conversation column', async () => { + await launch(() => ({ patches: [{ at: 0, entry: { kind: 'throw', chunks: [], message: 'upstream 503', code: 'SERVER' } }] }), + { mode: 'normal', maxRetries: 1, retryableCodes: ['SERVER'], + backoff: { initialDelayMs: 60_000, maxDelayMs: 60_000, jitterRatio: 0 } }) + await scaffold!.ctx.settings.update('ui-theme', { fontSize: 17 }) + const { settled } = await sendPrompt(90_000) + const retry = page.locator('details[data-active="true"]') + await retry.waitFor() + const originalStyle = await retry.getAttribute('style') + try { + const geometry = await retry.evaluate((element) => { + element.style.width = '200px' + const summary = element.querySelector('summary')! + const status = summary.querySelector('[role="status"]')! + return { width: summary.getBoundingClientRect().width, + scrollWidth: summary.scrollWidth, clientWidth: summary.clientWidth, + height: status.getBoundingClientRect().height, + lineHeight: Number.parseFloat(getComputedStyle(status).lineHeight) } + }) + expect(geometry.width).toBeLessThanOrEqual(200) + expect(geometry.scrollWidth).toBe(geometry.clientWidth) + expect(geometry.height).toBeGreaterThan(geometry.lineHeight) + expect(await retry.getByRole('status').textContent()).toContain('Retrying') + } finally { + await retry.evaluate((element, style) => { + if (style === null) element.removeAttribute('style') + else element.setAttribute('style', style) + }, originalStyle) + await page.getByRole('button', { name: 'Stop generating', exact: true }).click() + await settled + } + expect(tripwire.pageErrors).toEqual([]) + }) + it.skipIf(MODE === 'record')('recovers a transient SERVER failure through llm-retry and completes', async () => { const derived = deriveReplayScript(parseSessionLog(await readFile(FIXTURE, 'utf8'))) expect(derived).toHaveLength(1) @@ -366,7 +400,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { expect(turnEndReasons(sessionEvents).at(-1)).toBe('error') expect(sessionEvents.filter(e => e.type === 'llm/retry').length).toBe(2) await expect.poll(() => page.locator('[data-composer-input]').first().isEnabled(), { timeout: 10_000 }).toBe(true) - expect(await page.locator('[data-streaming="true"]').count()).toBe(0) + expect(await page.locator('[data-streaming="true"]:not([inert] *)').count()).toBe(0) // The terminal error row must render even though the turn owns a retry // chain: exhausted recovery shares the failing turn, so suppressing the // row by retry history would leave the failure invisible. diff --git a/apps/web/tests/seeded-history.e2e.ts b/apps/web/tests/seeded-history.e2e.ts index 8419aae826..5c24765398 100644 --- a/apps/web/tests/seeded-history.e2e.ts +++ b/apps/web/tests/seeded-history.e2e.ts @@ -372,7 +372,6 @@ describe('web e2e: seeded history renders through cold resume', () => { const processBottom = await process.evaluate(element => element.getBoundingClientRect().bottom) const answerTop = await page.getByText('DONE', { exact: true }).evaluate(element => element.getBoundingClientRect().top) - // Collapsed control row keeps its own 8px margin plus the 8px flow gap. expect(answerTop).toBe(processBottom + 16) expect(await page.getByText('Context compacted', { exact: true }).count()).toBe(0) // Tool cards render from logged tool/call + tool/result alone (views are diff --git a/apps/web/tests/shortcuts-desktop.e2e.ts b/apps/web/tests/shortcuts-desktop.e2e.ts index 6c37845f6f..9609e02de8 100644 --- a/apps/web/tests/shortcuts-desktop.e2e.ts +++ b/apps/web/tests/shortcuts-desktop.e2e.ts @@ -76,6 +76,11 @@ it.each([ if (await group.getAttribute('aria-expanded') !== 'true') await group.click() await page.getByRole('treeitem').nth(1).click() await page.getByText('DONE', { exact: true }).waitFor() + const renameDialog = page.getByRole('dialog', { name: 'Rename session', exact: true }) + await deliverPrimary('KeyG', true) + await renameDialog.getByLabel('Session name').waitFor() + await page.keyboard.press('Escape') + await renameDialog.waitFor({ state: 'hidden' }) const composer = page.locator('[data-composer-input]').first() await composer.focus() await page.keyboard.insertText('Desktop focus draft') @@ -272,6 +277,8 @@ it.each([ } satisfies DesktopShortcutInput) await page.getByText('DONE', { exact: true }).waitFor({ state: 'hidden' }) await expect.poll(() => page.getByRole('treeitem', { selected: true }).count()).toBe(0) + await deliverPrimary('KeyG', true) + expect(await renameDialog.count()).toBe(0) await page.getByRole('treeitem').nth(1).click() await page.getByText('DONE', { exact: true }).waitFor() await openReference() diff --git a/apps/web/tests/shortcuts-workspace.e2e.ts b/apps/web/tests/shortcuts-workspace.e2e.ts index e84cdc280d..139a0ad6a5 100644 --- a/apps/web/tests/shortcuts-workspace.e2e.ts +++ b/apps/web/tests/shortcuts-workspace.e2e.ts @@ -249,6 +249,29 @@ it.skipIf(mode === 'record').each([ if (await group.getAttribute('aria-expanded') !== 'true') await group.click() await page.getByRole('treeitem').nth(1).click() await page.getByText('DONE', { exact: true }).waitFor() + // Synthetic input observes browser ownership without navigating the fixture away. + expect(await page.evaluate((primary) => { + return [{ key: 'R', code: 'KeyR', shiftKey: true }, { key: ',', code: 'Comma', shiftKey: false }].map((key) => { + const event = new KeyboardEvent('keydown', { ...key, + metaKey: primary === 'Meta', ctrlKey: primary === 'Control', bubbles: true, cancelable: true }) + document.activeElement!.dispatchEvent(event) + return event.defaultPrevented + }) + }, primary)).toEqual([false, false]) + expect(await page.getByRole('dialog').count()).toBe(0) + const rename = page.getByRole('dialog', { name: 'Rename session', exact: true }) + expect(await rename.count()).toBe(0) + await page.keyboard.press(`${primary}+Alt+G`) + await rename.getByLabel('Session name').waitFor() + await page.keyboard.press('Escape') + await rename.waitFor({ state: 'hidden' }) + await page.keyboard.press(`${primary}+Alt+Comma`) + const settings = page.getByRole('dialog', { name: 'Settings', exact: true }) + await settings.waitFor() + await page.keyboard.press(`${primary}+Alt+G`) + expect(await rename.count()).toBe(0) + await page.keyboard.press('Escape') + await settings.waitFor({ state: 'hidden' }) await page.keyboard.press(`${primary}+Slash`) const reference = page.getByRole('dialog', { name: 'Keyboard shortcuts', exact: true }) await reference.waitFor() @@ -312,6 +335,8 @@ it.skipIf(mode === 'record').each([ await page.getByText('DONE', { exact: true }).waitFor({ state: 'hidden' }) await expect.poll(() => page.getByRole('treeitem', { selected: true }).count()).toBe(0) await page.locator('[data-composer-input]').first().waitFor() + await page.keyboard.press(`${primary}+Alt+G`) + expect(await rename.count()).toBe(0) expect(console.pageErrors).toEqual([]) } finally { await browser?.close() diff --git a/apps/web/tests/shortcuts.e2e.ts b/apps/web/tests/shortcuts.e2e.ts index 0bbe929b34..7a49455c70 100644 --- a/apps/web/tests/shortcuts.e2e.ts +++ b/apps/web/tests/shortcuts.e2e.ts @@ -32,8 +32,8 @@ describe('web e2e: shortcut reference', () => { await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' }) await page.waitForSelector('[class*="frame"]', { timeout: 30_000 }) const trigger = page.getByRole('button', { name: settings, exact: true }) - expect(await trigger.getAttribute('aria-keyshortcuts')).toBe(`${key}+,`) - const settingsHint = `${settings} ${key === 'Meta' ? '⌘ ,' : 'Ctrl + ,'}` + expect(await trigger.getAttribute('aria-keyshortcuts')).toBe(key === 'Meta' ? 'Alt+Meta+,' : 'Control+Alt+,') + const settingsHint = `${settings} ${key === 'Meta' ? '⌥ ⌘ ,' : 'Ctrl + Alt + ,'}` await trigger.hover() await page.getByRole('tooltip', { name: settingsHint, exact: true }).waitFor() await trigger.click() @@ -193,7 +193,7 @@ describe('web e2e: shortcut reference', () => { expect(await dialog.getByRole('listitem').count()).toBe(1) expect(await dialog.getByRole('listitem').textContent()).toContain(locale === 'zh-CN' ? '展开/收起左侧栏' : 'Toggle left sidebar') } - await page.keyboard.press(`${key}+Comma`) + await page.keyboard.press(`${key}+Alt+Comma`) expect(await referenceNode!.evaluate(element => element.isConnected)).toBe(true) expect(await page.getByRole('dialog').count()).toBe(2) expect(await page.getByRole('searchbox').inputValue()).toBe('toggle left sidebar') @@ -205,7 +205,7 @@ describe('web e2e: shortcut reference', () => { expect(await page.getByRole('button', { name: view, exact: true }).evaluate(element => element === document.activeElement)).toBe(true) expect(await page.getByRole('button', { name: view, exact: true }).getAttribute('data-dsh-automatic-focus')).toBe('') expect(await page.getByRole('button', { name: view, exact: true }).evaluate(element => getComputedStyle(element).outlineStyle)).toBe('none') - await page.keyboard.press(`${key}+Comma`) + await page.keyboard.press(`${key}+Alt+Comma`) await page.getByRole('dialog', { name: settings, exact: true }).waitFor({ state: 'hidden' }) expect(await page.getByRole('dialog').count()).toBe(0) expect(await trigger.evaluate(element => element === document.activeElement)).toBe(true) @@ -213,7 +213,7 @@ describe('web e2e: shortcut reference', () => { expect(await trigger.evaluate(element => getComputedStyle(element).outlineStyle)).toBe('none') await page.keyboard.press(referenceKey) await dialog.waitFor() - await page.keyboard.press(`${key}+Comma`) + await page.keyboard.press(`${key}+Alt+Comma`) expect(await page.getByRole('dialog', { name: settings, exact: true }).isVisible()).toBe(false) expect(await page.getByRole('dialog').count()).toBe(1) expect(await searchInput.evaluate(element => element === document.activeElement)).toBe(true) @@ -308,7 +308,7 @@ describe('web e2e: shortcut reference', () => { await dialog.getByRole('button', { name: locale === 'zh-CN' ? '恢复默认' : 'Restore default', exact: true }).click() await dialog.getByRole('group').waitFor({ state: 'hidden' }) await page.keyboard.press('Escape') - expect(await trigger.getAttribute('aria-keyshortcuts')).toBe(`${key}+,`) + expect(await trigger.getAttribute('aria-keyshortcuts')).toBe(key === 'Meta' ? 'Alt+Meta+,' : 'Control+Alt+,') await trigger.hover() await page.getByRole('tooltip', { name: settingsHint, exact: true }).waitFor() const sidebar = page.getByRole('button', { name: locale === 'zh-CN' ? '收起侧边栏' : 'Collapse sidebar', exact: true }) diff --git a/apps/web/tests/snapshots/streaming-fence-highlight/mid-stream.expected.md b/apps/web/tests/snapshots/streaming-fence-highlight/mid-stream.expected.md index dac0e202c7..d3078a0f56 100644 --- a/apps/web/tests/snapshots/streaming-fence-highlight/mid-stream.expected.md +++ b/apps/web/tests/snapshots/streaming-fence-highlight/mid-stream.expected.md @@ -12,8 +12,8 @@ - button "Wrap lines" [pressed] - button "Copy" - code: "const first: number = 1 const second = \"two\" let tail" -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - textbox "Message or run a task, / commands, @ files or sessions" - button "Add files or run commands" - 'button "Access mode, current: Workspace Write"': Workspace Write diff --git a/apps/web/tests/support.ts b/apps/web/tests/support.ts index fd1babcbef..b9f37c1382 100644 --- a/apps/web/tests/support.ts +++ b/apps/web/tests/support.ts @@ -136,7 +136,7 @@ export async function expandTurnProcesses(page: Page): Promise { } /** - * Expand the Turn process and secondary group containing a hidden descendant. + * Expand the settled Turn process and secondary group containing a hidden descendant. * @param page - page containing the Chat view. * @param target - descendant whose outer process disclosures should open. */ @@ -145,7 +145,10 @@ export async function expandOwningTurnProcess(page: Page, target: Locator): Prom const turn = await target.evaluate(element => element.closest('[data-chat-turn]')?.dataset.chatTurn) if (turn !== undefined) { const control = page.locator(`[data-turn-process="${turn}"]`) - if (await control.count() > 0 && await control.getAttribute('aria-expanded') === 'false') await control.click() + if (await control.count() > 0) { + await control.waitFor({ state: 'visible', timeout: 10_000 }) + if (await control.getAttribute('aria-expanded') === 'false') await control.click() + } } const group = target.locator('xpath=ancestor::*[@data-chat-group-key][1]') const header = group.locator('[data-process-activity]').first() diff --git a/docs/ui-radius.i18n.yaml b/docs/ui-radius.i18n.yaml index 33cf158dda..a5015aa946 100644 --- a/docs/ui-radius.i18n.yaml +++ b/docs/ui-radius.i18n.yaml @@ -15,8 +15,8 @@ en: cd8120cba5566e19 zh: 65359436088c8bc5 /dsh-unified-corner-radius-standard/select-the-radius: - en: 51cd57ec0fe8bb03 - zh: 53784eee8aa8be6e + en: 06ddd6bf9ef1e48c + zh: 4c42d0c9266a00e8 /dsh-unified-corner-radius-standard/reuse-tokens-and-preserve-deliberate-shapes: en: fb6d50f3139ad186 zh: 668cb0c02bd22167 diff --git a/docs/ui-radius.md b/docs/ui-radius.md index f9463627a5..d6d28568ca 100644 --- a/docs/ui-radius.md +++ b/docs/ui-radius.md @@ -40,7 +40,7 @@ Prefer an existing shared size variant when a proposed control falls between the Concrete defaults: `Button` uses H28/R8 for `sm` and H36/R12 for `md`, including outline variants. Standard H32 inputs and H36 selectors use R12. The Workspace files and New terminal guide entries both use R20, including title-only and described variants. Reserve R28 for their enclosing panel or dialog, not those entries. -Code blocks, diffs, and file previews share R16: changed-file entry cards, delivered-file cards, generic file attachment cards, hover preview panels, and the file-icon tiles inside entry cards. Their hover fills keep the same contour. +Code blocks, diffs, and file previews share R16: changed-file entry cards, delivered-file cards, generic file attachment cards, and hover preview panels. The 40px file-icon tiles inside changed-file and delivery cards use R10 on all four corners as a component-specific exception. Their hover fills keep the same contour. ## Reuse tokens and preserve deliberate shapes diff --git a/docs/ui-radius.zh.md b/docs/ui-radius.zh.md index 872a520681..657684ebc7 100644 --- a/docs/ui-radius.zh.md +++ b/docs/ui-radius.zh.md @@ -40,7 +40,7 @@ H 表示以 CSS 像素计、包含边框的设计外部高度。R 表示浏览 具体默认值:`Button` 的 `sm` 使用 H28/R8,`md` 使用 H36/R12,包含描边变体。标准 H32 输入框和 H36 选择器使用 R12。“工作区文件”和“新建终端”引导入口统一使用 R20,包含仅标题与带描述的变体。R28 留给包围它们的面板或对话框,不用于这些入口。 -代码块、diff 与文件预览共用 R16:包括改动文件入口卡片、交付文件卡片、通用附件文件卡片、悬停预览浮层,以及入口卡片内部的文件图标底框。各自的 hover 保持同一轮廓。 +代码块、diff 与文件预览共用 R16:包括改动文件入口卡片、交付文件卡片、通用附件文件卡片和悬停预览浮层。改动文件与交付文件卡片内部的 40px 文件图标底框作为组件特例,四角统一使用 R10。各自的 hover 保持同一轮廓。 ## 复用 token,保留有意设计的形状 diff --git a/packages/client/shortcuts/README.i18n.yaml b/packages/client/shortcuts/README.i18n.yaml index 60cc67c635..21adffeaa1 100644 --- a/packages/client/shortcuts/README.i18n.yaml +++ b/packages/client/shortcuts/README.i18n.yaml @@ -30,8 +30,8 @@ en: ca75c51c89c2c9b0 zh: a0e9f8d52b4b62ee /deepseek-ai-dsh-client-shortcuts/known-limitations-and-deferred-work: - en: 1fe209aad4915c64 - zh: 078d33d3ca9e05ea + en: 323e99bf933b4c6c + zh: 0a7ecdb2dcdb1288 /deepseek-ai-dsh-client-shortcuts/known-limitations-and-deferred-work/dev-note: en: 09894c06f9d76ac0 zh: d166e1338ae660e4 diff --git a/packages/client/shortcuts/README.md b/packages/client/shortcuts/README.md index 0fbe59a53c..61d0ac7000 100644 --- a/packages/client/shortcuts/README.md +++ b/packages/client/shortcuts/README.md @@ -79,7 +79,7 @@ None; this package neither assembles nor sends a provider request. -- Feature owners declare the Windows and macOS Web defaults explicitly: Mod+Slash, Mod+Comma, Mod+Backslash, and Control+Backquote stay two-key combinations; other defaults without Alt use Mod+Alt, and defaults with Alt use Mod+Shift except fullscreen uses Mod+Alt+Enter. macOS Web leaves Refresh current page unbound by default to preserve browser-specific Command+Option+R actions; its button and explicit user bindings remain available. Mod is Command on macOS and Control on Windows. Windows and macOS accept any three or four distinct modifiers in Web and Desktop. Web also accepts the listed combinations and Mod+Shift combinations; browser or system delivery requires platform testing. Linux Web accepts Mod+Slash, Mod+Shift+Comma, and Mod+Shift+Period. Operating-system shortcuts that do not reach the window cannot be intercepted. A chord does not delay or consume its first key while waiting for the second; a native action or focus change can therefore prevent completion. Command registration or disposal invalidates open edit drafts even when saved preferences are unchanged. Desktop rereads its file on every catalog update; there is no file watcher. +- Feature owners declare the Windows and macOS Web defaults explicitly: Mod+Slash, Mod+Backslash, and Control+Backquote stay two-key combinations; other defaults without Alt use Mod+Alt, and defaults with Alt use Mod+Shift except fullscreen uses Mod+Alt+Enter and session rename uses Mod+Alt+G. Web Settings uses Mod+Alt+Comma, leaving Mod+Comma to the browser. macOS Web leaves Refresh current page unbound by default to preserve browser-specific Command+Option+R actions; its button and explicit user bindings remain available. Mod is Command on macOS and Control on Windows. Windows and macOS accept any three or four distinct modifiers in Web and Desktop. Web also accepts the listed combinations and Mod+Shift combinations; browser or system delivery requires platform testing. Linux Web accepts Mod+Slash, Mod+Shift+Comma, and Mod+Shift+Period. Operating-system shortcuts that do not reach the window cannot be intercepted. A chord does not delay or consume its first key while waiting for the second; a native action or focus change can therefore prevent completion. Command registration or disposal invalidates open edit drafts even when saved preferences are unchanged. Desktop rereads its file on every catalog update; there is no file watcher. ### Dev Note diff --git a/packages/client/shortcuts/README.zh.md b/packages/client/shortcuts/README.zh.md index 5b5e19bbdf..30110aed87 100644 --- a/packages/client/shortcuts/README.zh.md +++ b/packages/client/shortcuts/README.zh.md @@ -79,7 +79,7 @@ Desktop 原生菜单和内嵌 frame 使用与 DOM 输入相同的命令注册表 -- 功能 owner 显式声明 Windows 和 macOS Web 的默认键位:Mod+Slash、Mod+Comma、Mod+Backslash 和 Control+Backquote 保持双键;其他不含 Alt 的默认键位使用 Mod+Alt,含 Alt 的使用 Mod+Shift,全屏除外,使用 Mod+Alt+Enter。macOS Web 的“刷新当前页”默认不绑定快捷键,以保留浏览器自身的 Command+Option+R 操作;刷新按钮和用户显式绑定仍然可用。Mod 在 macOS 上为 Command,在 Windows 上为 Control。Windows 和 macOS 的 Web、Desktop 均接受任意三个或四个不同修饰键的组合。Web 还接受上述组合及 Mod+Shift 组合;浏览器或系统能否送达按键需在对应平台测试。Linux Web 接受 Mod+Slash、Mod+Shift+Comma 和 Mod+Shift+Period。未送达窗口的操作系统快捷键无法被拦截。双键组合等待第二个键时,不延迟或消费第一个键;原生操作或焦点变化可能使组合无法完成。命令注册或注销会使打开的编辑草稿过期,即使保存的偏好未改变。Desktop 在每次命令目录更新时重新读取文件,不设置文件 watcher。 +- 功能 owner 显式声明 Windows 和 macOS Web 的默认键位:Mod+Slash、Mod+Backslash 和 Control+Backquote 保持双键;其他不含 Alt 的默认键位使用 Mod+Alt,含 Alt 的使用 Mod+Shift,其中全屏使用 Mod+Alt+Enter,会话重命名使用 Mod+Alt+G。Web 设置使用 Mod+Alt+Comma,将 Mod+Comma 留给浏览器。macOS Web 的“刷新当前页”默认不绑定快捷键,以保留浏览器自身的 Command+Option+R 操作;刷新按钮和用户显式绑定仍然可用。Mod 在 macOS 上为 Command,在 Windows 上为 Control。Windows 和 macOS 的 Web、Desktop 均接受任意三个或四个不同修饰键的组合。Web 还接受上述组合及 Mod+Shift 组合;浏览器或系统能否送达按键需在对应平台测试。Linux Web 接受 Mod+Slash、Mod+Shift+Comma 和 Mod+Shift+Period。未送达窗口的操作系统快捷键无法被拦截。双键组合等待第二个键时,不延迟或消费第一个键;原生操作或焦点变化可能使组合无法完成。命令注册或注销会使打开的编辑草稿过期,即使保存的偏好未改变。Desktop 在每次命令目录更新时重新读取文件,不设置文件 watcher。 ### 开发备注 diff --git a/packages/client/ui-chat/README.i18n.yaml b/packages/client/ui-chat/README.i18n.yaml index ace10677b3..4d0cfda86a 100644 --- a/packages/client/ui-chat/README.i18n.yaml +++ b/packages/client/ui-chat/README.i18n.yaml @@ -15,17 +15,17 @@ en: 41cd21266011d571 zh: 3172d1a2acbb27b7 /deepseek-ai-dsh-client-ui-chat/reference-previews: - en: 3fda3d75483ce7c1 - zh: 4d96ba654f70c021 + en: 0a1948349b1fbfd9 + zh: 56256e4d695e5192 /deepseek-ai-dsh-client-ui-chat/hidden-chat-rows: - en: 5706ae03ceacf251 + en: cbf1f0a2205046a5 zh: 2e59d0815351349f /deepseek-ai-dsh-client-ui-chat/command-and-failure-rows: en: 2157fcd6b0d100b5 zh: 9e865ac3953ebe30 /deepseek-ai-dsh-client-ui-chat/turn-token-usage: - en: 29346bd0f9a5ff51 - zh: 54dbed55f67aeecd + en: e1dd055d68ab25ea + zh: c5b62a22c9e457cb /deepseek-ai-dsh-client-ui-chat/completed-turn-footer: en: b4cd36e7ab91281e zh: 8ccf412b9dde61d5 @@ -33,8 +33,8 @@ en: 0ca3e8c4eab1ca78 zh: 1a1c71bab151eb7d /deepseek-ai-dsh-client-ui-chat/grouped-rendering: - en: f7b5260bea372361 - zh: 2daa3ff650ec9246 + en: 1ccc8de0d1ca0481 + zh: 156cdfb5b6ba6490 /deepseek-ai-dsh-client-ui-chat/scroll-ownership: en: 69b021c5a5d8b5a7 zh: 091ae7986c7a8a20 diff --git a/packages/client/ui-chat/README.md b/packages/client/ui-chat/README.md index 88c95680e6..ddbd9c7c6e 100644 --- a/packages/client/ui-chat/README.md +++ b/packages/client/ui-chat/README.md @@ -37,12 +37,12 @@ Chat supplies file and HTTP(S) navigation through one `MarkdownDelegateProvider` Standalone Markdown images show contained previews and open the shared image lightbox; local paths resolve against the viewed workspace after settlement. Image file links keep their sidebar activation and show a thumbnail after hover dwell or keyboard focus. Escape dismisses the thumbnail. Failed images retain a localized status and their description; no duplicate-image filtering is applied. -Settings → General → Open chat links in selects the destination for ordinary clicks on Chat HTTP(S) links: In-App Sidebar (default) opens a new right-Sidebar Browser tab, while Default Browser opens an external tab. The setting is shown only while the Sidebar Browser is available. If the Sidebar Browser is not registered, both choices use the external browser; modified clicks retain native behavior. The `ui-chat.linkOpening` preference persists on loopback browsers and stays process-local when settings cannot persist writes. Sent file references and skills confirmed by the message’s logged invocation also open in the right Sidebar. File paths use the viewed Session; skill names resolve through its current input-trigger source. Both use the prose file-link dotted underline on hover or focus. Sessions, directories, and command labels remain non-navigating references. +Settings → General → Open chat links in selects the destination for ordinary clicks on Chat HTTP(S) links: In-App Sidebar (default) opens a new right-Sidebar Browser tab, while Default Browser opens an external tab. The setting follows Keyboard shortcuts and is shown only while the Sidebar Browser is available. If the Sidebar Browser is not registered, both choices use the external browser; modified clicks retain native behavior. The `ui-chat.linkOpening` preference persists on loopback browsers and stays process-local when settings cannot persist writes. Sent file references and skills confirmed by the message’s logged invocation also open in the right Sidebar. File paths use the viewed Session; skill names resolve through its current input-trigger source. Both use the prose file-link dotted underline on hover or focus. Sessions, directories, and command labels remain non-navigating references. ## Hidden Chat rows -Chat omits system-prompt, ordinary Context injection, and `permission` command rows in every work-details mode. Context containing tool additions or removals remains visible. The filter changes neither recorded Session events nor Trajectory inspection. Non-human Turn triggers remain independent notices; other command rows remain in Chat. +Chat omits system-prompt, ordinary Context injection, and `permission` command rows in every work-details mode. Context containing tool additions or removals remains visible. The filter changes neither recorded Session events nor Trajectory inspection. Non-human Turn triggers remain independent, bordered notices with a contrasting neutral background; other command rows remain in Chat. When an Assistant attempt retires without a visible message, Chat hides its already-published Node instead of removing its key. A retry in the same Step reuses that key when visible content returns. This also applies when the loaded window lacks the Step start. @@ -58,7 +58,7 @@ Generic command rows retain the ordinary command glyph in every lifecycle state; A completed Turn shows an expandable usage row only when the loaded window includes `turn/start` and every started model attempt reports safe, exact usage. The row omits unavailable optional buckets. Incomplete or contradictory accounting hides the complete disclosure instead of presenting a partial total. -Settings → General → Performance & usage stores `ui-chat.performanceUsage` as `detailed` (default) or `compact`. Compact shows only available output speed and cache-hit percentage beneath the composer, without interactive statistic dialogs or per-Turn usage. Detailed exposes session statistics and per-Turn token usage. Neither mode shows elapsed time in the completed-turn footer. The preference changes presentation only; accounting and Session events remain intact. +Settings → General → Performance & usage appears immediately before Current version and stores `ui-chat.performanceUsage` as `detailed` (default) or `compact`. Compact shows only available output speed and cache-hit percentage beneath the composer, without interactive statistic dialogs or per-Turn usage. Detailed exposes session statistics and per-Turn token usage. Neither mode shows elapsed time in the completed-turn footer. The preference changes presentation only; accounting and Session events remain intact. On non-loopback browsers, the preference remains process-local because the settings scope cannot persist writes. Explicit selections update every consumer immediately; accepted Host settings reconcile the live value on loopback browsers. @@ -103,6 +103,8 @@ Each group owns local `useDisclosure` state that survives mode changes while its The Chat-node slot injects a reset-bound `useDisclosure` Hook for reasoning and tools. Intermediate renderers forward it without subscribing; each invocation owns independent open state. Source callbacks retain their receiver and stable identity. When an enclosing Turn actually hides a process member, its seat resets those disclosures without replacing component keys or changing the Hook reference. Display-mode changes preserve their open state. +Process rows share one text and icon color in every Work details mode: tertiary at rest and secondary on hover, while error and warning text keeps its semantic color. Leading icons, disclosure chevrons, and their boxes scale with the content font-size delta. + ----- diff --git a/packages/client/ui-chat/README.zh.md b/packages/client/ui-chat/README.zh.md index 55d8e30d70..490a44f6d8 100644 --- a/packages/client/ui-chat/README.zh.md +++ b/packages/client/ui-chat/README.zh.md @@ -37,7 +37,7 @@ Chat 在节点列表外通过一个 `MarkdownDelegateProvider` 提供文件及 H 独立的 Markdown 图片以内嵌预览显示,点击打开共享图片浮层;本地路径在消息落定后基于当前查看的工作区解析。图片文件链接保持点击打开侧栏,鼠标停留或键盘聚焦时显示缩略图,Esc 关闭缩略图。图片加载失败时保留本地化状态与图片说明;不执行重复图片过滤。 -设置 → 通用设置 → 网页链接默认打开方式控制普通点击 Chat HTTP(S) 链接时的目标:「应用内侧边栏」(默认)打开新的右侧 Sidebar Browser tab,「默认浏览器」打开外部标签页。该设置项仅在 Sidebar Browser 可用时显示。若 Sidebar Browser 未注册,两种选择均使用外部浏览器;带修饰键的点击保留原生行为。`ui-chat.linkOpening` 偏好在回环地址浏览器中持久化,设置无法持久化写入时仅在当前进程内生效。已发送的文件引用及消息日志确认调用的 skill 也可在右侧栏打开预览。文件路径使用当前查看的 Session;skill 名称由该 Session 当前的输入触发源解析。两者悬停或聚焦时均使用正文文件链接的虚线下划线。会话、目录和命令标签仍只作为引用展示。 +设置 → 通用设置 → 网页链接默认打开方式控制普通点击 Chat HTTP(S) 链接时的目标:「应用内侧边栏」(默认)打开新的右侧 Sidebar Browser tab,「默认浏览器」打开外部标签页。该设置项位于快捷键之后,仅在 Sidebar Browser 可用时显示。若 Sidebar Browser 未注册,两种选择均使用外部浏览器;带修饰键的点击保留原生行为。`ui-chat.linkOpening` 偏好在回环地址浏览器中持久化,设置无法持久化写入时仅在当前进程内生效。已发送的文件引用及消息日志确认调用的 skill 也可在右侧栏打开预览。文件路径使用当前查看的 Session;skill 名称由该 Session 当前的输入触发源解析。两者悬停或聚焦时均使用正文文件链接的虚线下划线。会话、目录和命令标签仍只作为引用展示。 ## Chat 隐藏的行 @@ -58,7 +58,7 @@ Assistant 尝试结束且没有可见消息时,Chat 隐藏已发布的 Node, 只有当已加载窗口包含 `turn/start`,且每次已启动的模型尝试都报告安全、精确的用量时,已完成轮次才显示可展开的用量行。该行会省略不可用的可选用量桶。记账不完整或相互矛盾时,整个详情都不显示,避免把部分总量冒充完整结果。 -设置 → 通用设置 → 性能与用量将 `ui-chat.performanceUsage` 保存为 `detailed`(默认)或 `compact`。简洁模式仅在输入框下方显示可用的输出速度和缓存命中率,不显示统计交互卡片或每轮用量。详细模式提供会话统计和每轮 token 用量。两种模式的已完成轮次页脚均不显示耗时。该偏好仅影响展示,记账和 Session 事件保持完整。 +设置 → 通用设置 → 性能与用量位于当前版本之前,将 `ui-chat.performanceUsage` 保存为 `detailed`(默认)或 `compact`。简洁模式仅在输入框下方显示可用的输出速度和缓存命中率,不显示统计交互卡片或每轮用量。详细模式提供会话统计和每轮 token 用量。两种模式的已完成轮次页脚均不显示耗时。该偏好仅影响展示,记账和 Session 事件保持完整。 在非回环地址浏览器中,设置作用域无法持久化写入,因此该偏好仅在当前进程内生效。明确选择会立即更新所有使用方;回环地址浏览器收到 Host 已接受的设置后会同步当前值。 @@ -103,6 +103,8 @@ Chat 通过 `uiConversation.groups` 注册过程 Group Definition。React 通过 Chat 节点 slot 为推理与工具注入绑定重置来源的 `useDisclosure` 钩子。中间 renderer 只透传、不订阅,每次调用拥有独立展开状态。来源回调保留接收对象及稳定引用。外层轮次实际隐藏过程成员时,所在节点重置这些开合状态,不替换组件 key,也不改变钩子引用。展示模式切换保留展开状态。 +过程行的文字与图标在所有工作过程展示模式下共用一种颜色:默认 tertiary、悬停 secondary,错误与警告文字保留语义色。前置图标、折叠箭头及其容器随正文字号增量缩放。 + ----- diff --git a/packages/client/ui-chat/src/client/apply.ts b/packages/client/ui-chat/src/client/apply.ts index 15752f6c70..9608c2b74a 100644 --- a/packages/client/ui-chat/src/client/apply.ts +++ b/packages/client/ui-chat/src/client/apply.ts @@ -133,7 +133,7 @@ export function apply(ctx: Context): void { scope.slots.inject('settings.general.item', () => scope.slots.register({ name: 'settings.general.item', id: 'link-opening', - order: 14, + order: 17, locale: NS, inject: (): LinkOpeningRowInjected => ({ hooks: { linkOpening, browserAvailable }, @@ -156,7 +156,7 @@ export function apply(ctx: Context): void { ctx.slots.inject('settings.general.item', () => ctx.slots.register({ name: 'settings.general.item', id: 'performance-usage', - order: 13, + order: 95, locale: NS, inject: (): PerformanceUsageRowInjected => ({ hooks: { performanceUsage }, diff --git a/packages/client/ui-chat/src/client/chat/ChatGroupSeat.module.css b/packages/client/ui-chat/src/client/chat/ChatGroupSeat.module.css index 3fd38dc21a..f40328906c 100644 --- a/packages/client/ui-chat/src/client/chat/ChatGroupSeat.module.css +++ b/packages/client/ui-chat/src/client/chat/ChatGroupSeat.module.css @@ -10,7 +10,7 @@ padding: 0; border: 0; background: none; - color: var(--dsw-alias-label-secondary); + color: var(--dsw-alias-label-tertiary); font: inherit; font-size: var(--dsh-content-font-size, 14px); text-align: left; @@ -19,18 +19,30 @@ } .title:hover { - color: var(--dsw-alias-label-primary); + color: var(--dsw-alias-label-secondary); } +/* The leading box and its glyph ride the body font-size delta so the icon + keeps its optical share of the row at every Settings size. */ .leading { position: relative; flex: none; display: inline-flex; align-items: center; justify-content: center; - width: 16px; - height: 16px; - color: var(--dsw-alias-label-tertiary); + width: calc(16px + var(--dsh-content-font-delta, 0px)); + height: calc(16px + var(--dsh-content-font-delta, 0px)); + color: inherit; +} + +.leading svg { + width: calc(14px + var(--dsh-content-font-delta, 0px)); + height: calc(14px + var(--dsh-content-font-delta, 0px)); +} + +.leading svg[width="16"] { + width: calc(16px + var(--dsh-content-font-delta, 0px)); + height: calc(16px + var(--dsh-content-font-delta, 0px)); } .activityIcon, diff --git a/packages/client/ui-chat/src/client/chat/GenericCommandCard.module.css b/packages/client/ui-chat/src/client/chat/GenericCommandCard.module.css index 6ef58c908d..d34df73398 100644 --- a/packages/client/ui-chat/src/client/chat/GenericCommandCard.module.css +++ b/packages/client/ui-chat/src/client/chat/GenericCommandCard.module.css @@ -7,13 +7,8 @@ flex-shrink: 0; } -.chevron { - color: var(--dsw-alias-label-secondary); -} - .title { font-weight: 400; - transition: color 100ms ease; } .separator { @@ -29,17 +24,10 @@ min-width: 0; overflow: hidden; flex: 1 1 auto; - color: var(--dsw-alias-label-tertiary); font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); text-overflow: ellipsis; white-space: nowrap; - transition: color 100ms ease; -} - -.row:hover .title, -.row:hover .summary:not([data-error]) { - color: var(--dsw-alias-label-primary); } .summary[data-error], diff --git a/packages/client/ui-chat/src/client/chat/GenericCommandCard.tsx b/packages/client/ui-chat/src/client/chat/GenericCommandCard.tsx index db4a1f4082..5a50e12a35 100644 --- a/packages/client/ui-chat/src/client/chat/GenericCommandCard.tsx +++ b/packages/client/ui-chat/src/client/chat/GenericCommandCard.tsx @@ -56,10 +56,8 @@ export const GenericCommandCard = memo(function GenericCommandCard({ node, t, ru {state === 'running' && {t('row.running')}} {state === 'error' && {t('row.failed')}} { clearInterval(timer) } }, [startTime]) const label = startTime === undefined ? t('chat.deepDiving') : t('chat.deepDivingFor', { - duration: formatLiveRunDuration(Math.max(1000, now - startTime), t), + duration: formatRunDuration(Math.max(1000, now - startTime), t).map(part => part.text).join(''), }) return (
diff --git a/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.module.css b/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.module.css index aedb0b6a4f..c3c0a309d7 100644 --- a/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.module.css +++ b/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.module.css @@ -10,6 +10,8 @@ border-bottom: 0.5px solid var(--dsw-alias-border-l2); background: none; color: var(--dsw-alias-label-tertiary); + font: inherit; + font-size: var(--dsh-content-font-size, 14px); cursor: pointer; text-align: left; transition: color 100ms ease; @@ -19,8 +21,8 @@ cursor: default; } -.root:not(:disabled):hover { - color: var(--dsw-alias-label-primary); +.root:hover:not(:disabled) { + color: var(--dsw-alias-label-secondary); } .chevron { @@ -28,7 +30,7 @@ width: 14px; height: 14px; margin-left: 4px; - color: var(--dsw-alias-label-caption); + color: inherit; transition: transform 100ms ease; } @@ -39,12 +41,17 @@ .label { min-width: 0; overflow: hidden; - font-size: var(--dsh-content-font-size-secondary, 13px); + font-size: inherit; line-height: calc(24px + var(--dsh-content-font-delta, 0px)); text-overflow: ellipsis; white-space: nowrap; } +.durationNumber { + font-family: var(--ds-font-family-code); + font-variant-numeric: tabular-nums; +} + @media (prefers-reduced-motion: reduce) { .root, .chevron { diff --git a/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.tsx b/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.tsx index d1e0fc5eae..e897b061e0 100644 --- a/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.tsx +++ b/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.tsx @@ -6,7 +6,7 @@ import { formatRunDuration } from './message-chrome.ts' import a11yCss from './accessibility.module.css' import css from './TurnProcessNodeView.module.css' -/** Turn-level process disclosure controller. */ +/** Settled Turn duration and process disclosure above its content. */ export const TurnProcessNodeView = memo(function TurnProcessNodeView({ node, turnProcess, t, }: ChatNodeViewProps<'turn-process'>) { @@ -20,13 +20,13 @@ export const TurnProcessNodeView = memo(function TurnProcessNodeView({ const reason = turn.end?.data.reason.kind const elapsedMs = turn.start === undefined || turn.end === undefined ? undefined : Math.max(1000, turn.end.time - turn.start.time) - const duration = elapsedMs === undefined ? undefined + const duration = elapsedMs === undefined || reason === 'aborted' || reason === 'error' ? undefined : formatRunDuration(elapsedMs, t) // Other end reasons retain elapsed time; only cancellation and failure replace it. const label = reason === 'aborted' ? t('message.stopped') : reason === 'error' ? t('message.turnProcess.failed') : duration === undefined ? t('message.turnProcess.worked') - : t('message.turnProcess.took', { duration }) + : t('message.turnProcess.took') const announcement = reason === 'aborted' ? t('message.stopped') : reason === 'error' ? t('message.turnProcess.failed') : t('message.turnProcess.worked') @@ -48,7 +48,12 @@ export const TurnProcessNodeView = memo(function TurnProcessNodeView({ turnProcess.setOpen(!open) }} > - {label} + + {label} + {duration?.map((part, index) => ( + {part.text} + ))} + {canCollapse && } diff --git a/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.module.css b/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.module.css index 7ec9921fb1..bc9ec01f14 100644 --- a/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.module.css +++ b/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.module.css @@ -2,10 +2,10 @@ min-width: 0; border: 0.5px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-xl); - background: var(--dsw-alias-markdown-code-block); + background: var(--dsw-alias-turn-trigger-bg, var(--dsw-alias-markdown-code-block)); transition: background-color 100ms ease; } -.root:hover { background: var(--dsw-alias-interactive-bg-hover); } +.root:hover { background: var(--dsw-alias-turn-trigger-bg-hover, var(--dsw-alias-interactive-bg-hover)); } .header { display: flex; align-items: center; @@ -14,20 +14,22 @@ padding: 12px 16px; border: 0; background: none; - color: var(--dsw-alias-label-primary); + color: var(--dsw-alias-label-tertiary); + transition: color 100ms ease; font: inherit; text-align: left; cursor: pointer; } -.icon { display: inline-flex; flex: none; color: var(--dsw-alias-label-tertiary); } -.title { flex: none; font: var(--dsw-font-xs-13); } -.time { flex: none; margin-left: auto; color: var(--dsw-alias-label-caption); font: var(--dsw-font-xxs-12); } -.chevron, .openChevron { flex: none; color: var(--dsw-alias-label-tertiary); } +.header:hover { color: var(--dsw-alias-label-secondary); } +.icon { display: inline-flex; flex: none; color: inherit; } +.title { flex: none; font-size: var(--dsh-content-font-size, 14px); } +.time { flex: none; margin-left: auto; color: inherit; font: var(--dsw-font-xxs-12); } +.chevron, .openChevron { flex: none; color: inherit; } .openChevron { transform: rotate(180deg); } .body { padding: 0 16px 12px 40px; } .explanation { margin: 8px 0; color: var(--dsw-alias-label-secondary); font: var(--dsw-font-xxs-12); } .content { max-height: 240px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font: var(--dsw-font-xxs-12); } @media (prefers-reduced-motion: reduce) { - .root { transition: none; } + .root, .header { transition: none; } } diff --git a/packages/client/ui-chat/src/client/chat/message-chrome.ts b/packages/client/ui-chat/src/client/chat/message-chrome.ts index 7f6826fc96..e078d28177 100644 --- a/packages/client/ui-chat/src/client/chat/message-chrome.ts +++ b/packages/client/ui-chat/src/client/chat/message-chrome.ts @@ -7,7 +7,7 @@ export type ClockTranslate = Translate<'clock.md' | 'clock.ymd'> /** The elapsed-duration share of the conversation dictionary. */ export type RunDurationTranslate = - Translate<'duration.seconds' | 'duration.minutes' | 'duration.hours'> + Translate<'duration.secondUnit' | 'duration.minuteUnit' | 'duration.hourUnit'> /** Refresh interval for whole-second live run clocks. */ export const LIVE_RUN_CLOCK_INTERVAL_MS = 1000 @@ -38,43 +38,29 @@ export function msUntilNextLocalMidnight(ms: number): number { return Math.max(next.getTime() - ms, 1) } +/** One numeric value or localized unit in an elapsed-time label. */ +export interface RunDurationPart { + readonly text: string + readonly numeric: boolean +} + /** - * Localized elapsed-time label for the running conversation clock. - * @param ms - Elapsed duration in milliseconds (negatives clamp to zero). - * @param t - Translate seat supplying the duration templates. - * @returns Display string in whole seconds; minutes and seconds once the - * duration reaches a minute; hours, minutes, and seconds once it reaches an - * hour, with the smaller units zero-padded. + * Build elapsed-time fragments for both live and completed Turn labels. + * @param ms - elapsed milliseconds; negatives clamp to zero and fractions floor. + * @param t - translate seat supplying units and their trailing spacing. + * @returns numbers and localized units in display order, without leading zeros; + * minutes start at 60 seconds and hours at 60 minutes. */ -export function formatRunDuration(ms: number, t: RunDurationTranslate): string { +export function formatRunDuration(ms: number, t: RunDurationTranslate): RunDurationPart[] { const total = Math.max(0, Math.floor(ms / 1000)) const hours = Math.floor(total / 3600) const minutes = Math.floor(total / 60) % 60 const seconds = total % 60 - if (hours > 0) { - return t('duration.hours', { hours, minutes: pad2(minutes), seconds: pad2(seconds) }) - } - return minutes > 0 - ? t('duration.minutes', { minutes, seconds: pad2(seconds) }) - : t('duration.seconds', { seconds }) -} - -/** - * Localized live elapsed time without padded seconds or early rollover. - * @param ms - Elapsed duration in milliseconds (negatives clamp to zero). - * @param t - Translate seat supplying the duration templates. - * @returns Whole seconds without a leading zero; minutes start at 60 seconds - * and hours start at exactly 60 minutes. - */ -export function formatLiveRunDuration(ms: number, t: RunDurationTranslate): string { - const totalSeconds = Math.max(0, Math.floor(ms / 1000)) - const hours = Math.floor(totalSeconds / 3600) - const minutes = Math.floor(totalSeconds / 60) % 60 - const seconds = String(totalSeconds % 60) - if (hours > 0) return t('duration.hours', { hours, minutes: pad2(minutes), seconds }) - return minutes > 0 - ? t('duration.minutes', { minutes, seconds }) - : t('duration.seconds', { seconds }) + const parts: RunDurationPart[] = [] + if (hours > 0) parts.push({ text: String(hours), numeric: true }, { text: t('duration.hourUnit'), numeric: false }) + if (total >= 60) parts.push({ text: String(minutes), numeric: true }, { text: t('duration.minuteUnit'), numeric: false }) + parts.push({ text: String(seconds), numeric: true }, { text: t('duration.secondUnit'), numeric: false }) + return parts } /** diff --git a/packages/client/ui-chat/src/client/conversation-nodes/README.i18n.yaml b/packages/client/ui-chat/src/client/conversation-nodes/README.i18n.yaml index 7255b38160..f9fe4e3545 100644 --- a/packages/client/ui-chat/src/client/conversation-nodes/README.i18n.yaml +++ b/packages/client/ui-chat/src/client/conversation-nodes/README.i18n.yaml @@ -24,8 +24,8 @@ en: 91b35cbebe8c2a1d zh: a4fee6d7102bc874 /chat-conversation-node-rules/whole-turn-folding: - en: 69989cc6a42c137a - zh: ceca703426315b88 + en: f8674438930c268c + zh: cb8d007db714de6f /chat-conversation-node-rules/display-modes: en: ada5a60d2c46f6a8 zh: f3eaeb5cb4d9b3de @@ -33,8 +33,8 @@ en: a47e16132ba3c736 zh: fbed2b6f43a93287 /chat-conversation-node-rules/display-modes/group-title-rules: - en: 49fc1aa60cbd239e - zh: 44b0f9a96ec5b112 + en: e4b8384f32e4ff98 + zh: e8a42f078b38f41b /chat-conversation-node-rules/process-grouping: en: 276a97b95610f5e8 zh: 7627b2afb0a7c0f4 diff --git a/packages/client/ui-chat/src/client/conversation-nodes/README.md b/packages/client/ui-chat/src/client/conversation-nodes/README.md index da4d691a00..10d46adb7b 100644 --- a/packages/client/ui-chat/src/client/conversation-nodes/README.md +++ b/packages/client/ui-chat/src/client/conversation-nodes/README.md @@ -101,6 +101,8 @@ Paging adds older content above the retained anchor without jumping to the new t While the Session runs, Chat appends a Session-level indicator at the bottom of the current transcript, after pending submission or steering rows: a whale tail beside shimmering elapsed time. Before the current Turn start time arrives, the indicator shows status text without a duration. The clock updates in whole seconds, starts at one second, and uses hours from 60 minutes. When the Session stops running, the indicator disappears; the closed Turn process control shows the fixed duration, cancellation, or failure status. Lifecycle changes have a polite announcement; clock ticks do not. The whale is hidden from assistive technology and remains static under reduced motion. +The top duration/status control and trigger title use the group-title font size and follow the font-size setting. Completed duration digits use the code font with tabular numerals; running and completed minutes and seconds have no leading zero. The timed running label ends with a space and “···”; completion shows “Completed in”. + Automatic collapse keeps the process open if hiding it would hide keyboard focus. Manual closing focuses the process control before hiding its members. Closing a whole Turn resets its groups and inner reasoning/tool disclosures; it does not reset unrelated renderer state. Browser find can reveal searchable hidden content. ----- @@ -165,7 +167,7 @@ The labels below describe recorded activity, not successful outcomes. For exampl English lowercases the initial letter of joined labels after the first. Closing a group immediately selects the completed summary; the 150ms minimum applies to running-title changes, not to delaying completion. Detailed hides group headers in running Turns, including groups ended by a reply or steering before their Turn ends. Verbose also hides historical group headers. -Group headers show a category icon, replace it with a down arrow on hover or keyboard focus, and show an up arrow while open. Manually expanded group bodies use 8px row spacing, a `min(400px, 50vh)` height cap, and 24px directional fades. Wheel scrolling can continue into the outer transcript at an edge. Detailed removes the group-level cap and uses 16px row spacing in running Turns; Verbose applies this layout to historical Turns as well. +Group headers show a category icon, replace it with a down arrow on hover or keyboard focus, and show an up arrow while open. Manually expanded group bodies use 6px row spacing, a `min(400px, 50vh)` height cap, and 24px directional fades. Wheel scrolling can continue into the outer transcript at an edge. Detailed removes the group-level cap in running Turns; Verbose applies this layout to historical Turns as well. Process rows retain 6px spacing in both modes; Assistant responses have 12px clearance. Expanded group titles sit 8px above their content; Turn process controls retain 16px clearance. An open capped group follows content growth only while its own scroll position is at the bottom. Scrolling away pauses that group's following; returning to the bottom resumes it, independently of outer transcript following. Manually opening an unclosed group starts at the bottom and follows growth; manually opening a closed group starts at the top with following disabled, even when its initial content fits without scrolling. Closing the group in the data or restoring its height cap through a mode change does not reset an already-open reader's position. Browser find retains its own reveal position. diff --git a/packages/client/ui-chat/src/client/conversation-nodes/README.zh.md b/packages/client/ui-chat/src/client/conversation-nodes/README.zh.md index ab4b3c05f4..ee00c6e956 100644 --- a/packages/client/ui-chat/src/client/conversation-nodes/README.zh.md +++ b/packages/client/ui-chat/src/client/conversation-nodes/README.zh.md @@ -101,6 +101,8 @@ description: "Chat 过程分组与活动摘要的明细业务规则。" 会话运行期间,Chat 在当前 transcript 最底部、待提交或 steering 行之后附加 Session 级运行指示:鲸鱼摆尾旁显示闪烁的已用时间。当前 Turn 的开始时间到达前,运行指示只显示状态文字,不显示时长。时钟按整秒更新,最少显示一秒,满 60 分钟使用小时。会话停止运行后,该指示消失;已结束 Turn 的过程控件显示固定时长、取消或失败状态。生命周期变化有礼貌播报,时钟跳动不播报。鲸鱼对辅助技术隐藏,启用减少动态效果时保持静态。 +顶部用时/状态控件与唤醒标题沿用二级汇总标题字号,并跟随字号设置。已完成用时的数字使用代码等宽字体;运行和完成状态的分钟和秒数均不补前导零。含用时的运行文案末尾附加一个空格及“···”,完成后显示“已完成,用时”。 + 自动收起若会隐藏键盘焦点,则保持过程展开。手动收起先将焦点移到过程控件,再隐藏成员。收起整轮会重置内部组及推理、工具的开合,不重置渲染器的其他状态。浏览器查找可以显示可搜索的隐藏内容。 ----- @@ -165,7 +167,7 @@ description: "Chat 过程分组与活动摘要的明细业务规则。" 英文组合标题将第一项之后各项的首字母转为小写。组结束时立即切换完成摘要;150ms 最短保留时间针对运行中标题变化,不延迟完成状态。详细模式隐藏运行中轮次的组头,包括被回复或 steering 截断、但所属轮次尚未结束的小组;完全展开模式也隐藏历史组头。 -组头显示类别图标,悬停或键盘聚焦时替换为向下箭头,展开时显示向上箭头。手动展开的组正文使用 8px 行间距、`min(400px, 50vh)` 限高及 24px 方向渐隐;到达边缘时滚轮可以继续滚动外层文本记录。详细模式对运行中轮次取消组级限高,使用 16px 行间距;完全展开模式对历史轮次也采用该布局。 +组头显示类别图标,悬停或键盘聚焦时替换为向下箭头,展开时显示向上箭头。手动展开的组正文使用 6px 行间距、`min(400px, 50vh)` 限高及 24px 方向渐隐;到达边缘时滚轮可以继续滚动外层文本记录。详细模式对运行中轮次取消组级限高;完全展开模式对历史轮次也采用该布局。两种模式下过程行均保留 6px 间距,Assistant 回复周围保留 12px 间距。展开组头与内容相隔 8px,整轮过程控件周围保留 16px 间距。 已展开的限高组仅在自己的滚动位置处于底部时跟随内容增长。滚离底部暂停该组跟随,滚回底部后恢复,与外层文本记录的跟随状态独立。手动展开未关闭的组时定位到底部并跟随增长;手动展开已关闭的组时从顶部开始且不启用跟随,即使初始内容无需滚动即可容纳。数据中的组变为已关闭,或通过模式切换恢复限高时,不重置已展开组的阅读位置。浏览器查找沿用自身的展开定位。 diff --git a/packages/client/ui-chat/src/client/locale.ts b/packages/client/ui-chat/src/client/locale.ts index dbec14300e..4c4bfa7e12 100644 --- a/packages/client/ui-chat/src/client/locale.ts +++ b/packages/client/ui-chat/src/client/locale.ts @@ -61,8 +61,8 @@ export const zh = { 'message.trigger.job': '后台任务状态更新', 'message.trigger.plugin': '插件状态更新', 'message.trigger.explanation': '这条通知触发了本轮回复。', - 'message.turnProcess.worked': '已完成工作', - 'message.turnProcess.took': '用时 {duration}', + 'message.turnProcess.worked': '已完成', + 'message.turnProcess.took': '已完成,用时 ', 'message.turnProcess.failed': '处理失败', 'view.chat': '对话', 'image.open': '查看大图', @@ -86,8 +86,8 @@ export const zh = { 'chat.loadError': '历史加载失败:{message}({code})', 'chat.loadOlder': '加载更早', 'chat.toBottom': '回到底部', - 'chat.deepDiving': '深度求索中...', - 'chat.deepDivingFor': '深度求索中,用时 {duration}...', + 'chat.deepDiving': '深度求索中', + 'chat.deepDivingFor': '深度求索中,用时 {duration} ···', 'chat.turnNavigation.label': '轮次导航', 'chat.turnNavigation.jump': '跳转到第 {turn} 轮', 'chat.turnNavigation.jumpLoad': '加载并跳转到第 {turn} 轮', @@ -179,9 +179,9 @@ export const zh = { 'message.turnUsage.output': '输出', 'message.turnUsage.reasoning': '(其中推理 {tokens})', 'message.turnUsage.count': '{count} tok', - 'duration.seconds': '{seconds}秒', - 'duration.minutes': '{minutes}分{seconds}秒', - 'duration.hours': '{hours}小时{minutes}分{seconds}秒', + 'duration.secondUnit': '秒', + 'duration.minuteUnit': '分', + 'duration.hourUnit': '小时', 'command.running': '执行中…', 'command.failed': '指令失败', 'command.done': '已完成', @@ -254,8 +254,8 @@ export const en = { 'message.trigger.job': 'Background task updated', 'message.trigger.plugin': 'Plugin status updated', 'message.trigger.explanation': 'This notification triggered this response.', - 'message.turnProcess.worked': 'Worked', - 'message.turnProcess.took': 'Took {duration}', + 'message.turnProcess.worked': 'Completed', + 'message.turnProcess.took': 'Completed in ', 'message.turnProcess.failed': 'Failed', 'view.chat': 'Chat', 'image.open': 'View full image', @@ -279,8 +279,8 @@ export const en = { 'chat.loadError': 'Failed to load history: {message} ({code})', 'chat.loadOlder': 'Load earlier', 'chat.toBottom': 'Back to bottom', - 'chat.deepDiving': 'Deep diving...', - 'chat.deepDivingFor': 'Deep diving for {duration}...', + 'chat.deepDiving': 'Deep diving', + 'chat.deepDivingFor': 'Deep diving for {duration} ···', 'chat.turnNavigation.label': 'Turn navigation', 'chat.turnNavigation.jump': 'Jump to turn {turn}', 'chat.turnNavigation.jumpLoad': 'Load and jump to turn {turn}', @@ -372,9 +372,9 @@ export const en = { 'message.turnUsage.output': 'Output', 'message.turnUsage.reasoning': ' ({tokens} reasoning)', 'message.turnUsage.count': '{count} tok', - 'duration.seconds': '{seconds}s', - 'duration.minutes': '{minutes}m {seconds}s', - 'duration.hours': '{hours}h {minutes}m {seconds}s', + 'duration.secondUnit': 's', + 'duration.minuteUnit': 'm ', + 'duration.hourUnit': 'h ', 'command.running': 'Running…', 'command.failed': 'Command failed', 'command.done': 'Completed', diff --git a/packages/client/ui-chat/tests/chat-apply.client.spec.tsx b/packages/client/ui-chat/tests/chat-apply.client.spec.tsx index 98df8ebccc..fbd78cab31 100644 --- a/packages/client/ui-chat/tests/chat-apply.client.spec.tsx +++ b/packages/client/ui-chat/tests/chat-apply.client.spec.tsx @@ -129,7 +129,7 @@ describe('Chat apply wiring', () => { expect(b.runtime.slots.entries('conversation.composer.dock').map(row => row.options.id)) .toEqual(['stats']) expect(b.runtime.slots.entries('settings.general.item').map(row => row.options.id)) - .toEqual(['transcript-view', 'performance-usage', 'link-opening', 'composer-enter']) + .toEqual(['transcript-view', 'link-opening', 'composer-enter', 'performance-usage']) await b.runtime.dispose() }) diff --git a/packages/client/ui-chat/tests/chat-view.client.spec.tsx b/packages/client/ui-chat/tests/chat-view.client.spec.tsx index f27271b3b5..f9da5bf6be 100644 --- a/packages/client/ui-chat/tests/chat-view.client.spec.tsx +++ b/packages/client/ui-chat/tests/chat-view.client.spec.tsx @@ -45,7 +45,7 @@ import { import { TurnTailNodeView } from '../src/client/chat/TurnTailNodeView.tsx' import { TurnProcessNodeView } from '../src/client/chat/TurnProcessNodeView.tsx' import { SystemPromptNodeView } from '../src/client/chat/SystemPromptRow.tsx' -import { formatRunDuration } from '../src/client/chat/message-chrome.ts' +import { formatRunDuration as durationParts } from '../src/client/chat/message-chrome.ts' import { ChatSnapshotBuilder } from '../src/client/conversation-nodes/chat-snapshot-builder.ts' import { ProcessState } from '../src/client/conversation-nodes/process-groups.ts' import type { TurnProcessSpec } from '../src/client/contract/turn-process.ts' @@ -571,6 +571,10 @@ function installScrollMetrics(element: HTMLElement, initialHeight: number, clien } } +function formatRunDuration(ms: number, t: Parameters[1]): string { + return durationParts(ms, t).map(part => part.text).join('') +} + describe('Chat node rendering', () => { it('opens Markdown references to unmodified files with line navigation', () => { @@ -623,17 +627,17 @@ describe('Chat node rendering', () => { expect(formatRunDuration(0, t)).toBe('0秒') expect(formatRunDuration(-500, t)).toBe('0秒') expect(formatRunDuration(15_999, t)).toBe('15秒') - expect(formatRunDuration(125_000, t)).toBe('2分05秒') + expect(formatRunDuration(125_000, t)).toBe('2分5秒') // The hour rolls at exactly 3600s, never at 60 displayed minutes. expect(formatRunDuration(3_599_999, t)).toBe('59分59秒') - expect(formatRunDuration(3_600_000, t)).toBe('1小时00分00秒') - expect(formatRunDuration(3_903_000, t)).toBe('1小时05分03秒') - expect(formatRunDuration(7_261_000, t)).toBe('2小时01分01秒') + expect(formatRunDuration(3_600_000, t)).toBe('1小时0分0秒') + expect(formatRunDuration(3_903_000, t)).toBe('1小时5分3秒') + expect(formatRunDuration(7_261_000, t)).toBe('2小时1分1秒') }) it('formatRunDuration uses the English hour template', () => { const t = makeTranslate(en, commonEn) - expect(formatRunDuration(3_903_000, t)).toBe('1h 05m 03s') + expect(formatRunDuration(3_903_000, t)).toBe('1h 5m 3s') }) }) @@ -763,7 +767,7 @@ describe('ChatView', () => { expect(headers.every(header => header.closest('[hidden]') !== null)).toBe(true) expect(bodies.every(body => !body.hasAttribute('hidden'))).toBe(true) expect(control.disabled).toBe(true) - expect(control.textContent).toContain(h.props.t('message.turnProcess.took', { duration: formatRunDuration(3_000, h.props.t) })) + expect(control.textContent).toContain(h.props.t('message.turnProcess.took') + formatRunDuration(3_000, h.props.t)) fireEvent.click(control) expect(bodies.every(body => !body.hasAttribute('hidden'))).toBe(true) expect([...view.container.querySelectorAll('[data-chat-group-key]')]).toEqual(roots) @@ -1624,7 +1628,7 @@ describe('ChatView', () => { expect(writeText).toHaveBeenCalledWith('interrupt now') expect(within(pendingBubble as HTMLElement).queryByRole('button', { name: '在新对话中分支' })).toBeNull() expect(turnProcessControl(view.container)).toBeNull() - expect(runningContent(view.container)?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(runningContent(view.container)?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) expect(view.getByRole('status').compareDocumentPosition(view.getByText('interrupt now')) & Node.DOCUMENT_POSITION_PRECEDING).not.toBe(0) @@ -2204,7 +2208,7 @@ describe('ChatView', () => { turnEnds: new Map([[1, 6]]), }) const view = render() - const toggle = view.getByRole('button', { name: '用时 4秒' }) + const toggle = view.getByRole('button', { name: '已完成,用时 4秒' }) expect(toggle.getAttribute('aria-expanded')).toBe('false') expect(toggle.getAttribute('data-turn-process-tool-calls')).toBe('1') expect(toggle.getAttribute('data-turn-process-messages')).toBe('1') @@ -2230,12 +2234,12 @@ describe('ChatView', () => { expect(members.map(member => member.getAttribute('hidden'))).toEqual([null, null, null]) act(() => { h.set({ nodes: [user(1, 'question'), first] }) }) - expect(view.getByRole('button', { name: '用时 4秒' }).getAttribute('aria-expanded')).toBe('false') + expect(view.getByRole('button', { name: '已完成,用时 4秒' }).getAttribute('aria-expanded')).toBe('false') expect(members[0]?.getAttribute('hidden')).toBeNull() act(() => { h.set({ nodes: [user(1, 'question'), first, toolResult(3, 'a'), toolResult(4, 'b', 'subagent'), second], }) }) - const renewedToggle = view.getByRole('button', { name: '用时 4秒' }) + const renewedToggle = view.getByRole('button', { name: '已完成,用时 4秒' }) expect(renewedToggle.getAttribute('aria-expanded')).toBe('true') expect(members[0]?.getAttribute('hidden')).toBeNull() }) @@ -2277,7 +2281,7 @@ describe('ChatView', () => { const view = render() expect(renderedFlowKinds(view.container)).toEqual(['user', 'turn-process']) expect(turnProcessControl(view.container)).toBeNull() - expect(runningContent(view.container)?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(runningContent(view.container)?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) expect(view.container.querySelector('[data-chat-flow-kind="system-prompt"]')).toBeNull() act(() => { @@ -2337,7 +2341,7 @@ describe('ChatView', () => { turnEnds: new Map([[1, 4]]), }) const view = render() - const toggle = view.getByRole('button', { name: '已完成工作' }) as HTMLButtonElement + const toggle = view.getByRole('button', { name: '已完成' }) as HTMLButtonElement const contextRow = view.container.querySelector('[data-chat-flow-kind="context"]') expect(toggle.disabled).toBe(true) @@ -2460,7 +2464,7 @@ describe('ChatView', () => { }) const view = render() expect(turnProcessControl(view.container)).toBeNull() - expect(runningContent(view.container)?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(runningContent(view.container)?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) const processRow = view.getByText('inspect').closest('[data-chat-flow-kind="assistant-step"]') as HTMLElement expect(processRow.getAttribute('hidden')).toBeNull() @@ -2474,14 +2478,14 @@ describe('ChatView', () => { }) const toggle = turnProcessControl(view.container)! expect(runningContent(view.container)).toBeNull() - expect(toggle.textContent).toBe('用时 5秒') + expect(toggle.textContent).toBe('已完成,用时 5秒') expect(toggle.disabled).toBe(false) expect(toggle.getAttribute('aria-expanded')).toBe('false') expect(processRow.getAttribute('hidden')).toBe('until-found') fireEvent.click(toggle) act(() => { vi.advanceTimersByTime(1_000) }) expect(toggle.getAttribute('aria-expanded')).toBe('true') - expect(toggle.textContent).toBe('用时 5秒') + expect(toggle.textContent).toBe('已完成,用时 5秒') expect(processRow.getAttribute('hidden')).toBeNull() })) @@ -2527,7 +2531,7 @@ describe('ChatView', () => { turnEnds: new Map([[1, 4]]), }) const view = render() - const toggle = view.getByRole('button', { name: '已完成工作' }) + const toggle = view.getByRole('button', { name: '已完成' }) const reasoning = view.container.querySelector('[data-turn-process-inline]') expect(toggle.getAttribute('aria-expanded')).toBe('false') expect(reasoning?.getAttribute('hidden')).toBe('until-found') @@ -2591,7 +2595,7 @@ describe('ChatView', () => { }) }) const toggle = turnProcessControl(view.container)! expect(toggle.getAttribute('aria-expanded')).toBe('false') - expect(toggle.textContent).toBe('已完成工作') + expect(toggle.textContent).toBe('已完成') expect(view.container.querySelector('[data-chat-node-key="fixture:assistant:2"]')).toBe(processRow) expect(processRow.getAttribute('hidden')).toBe('until-found') expect(view.getByLabelText('回到底部')).toBeTruthy() @@ -2659,7 +2663,7 @@ describe('ChatView', () => { }) }) expect(turnProcessControl(view.container)).toBe(toggle) expect(toggle.getAttribute('aria-expanded')).toBe('false') - expect(toggle.textContent).toBe(h.props.t('message.turnProcess.took', { duration: formatRunDuration(5_000, h.props.t) })) + expect(toggle.textContent).toBe(h.props.t('message.turnProcess.took') + formatRunDuration(5_000, h.props.t)) expect(row.getAttribute('hidden')).toBe('until-found') }) @@ -2770,7 +2774,7 @@ describe('ChatView', () => { expect(view.container.querySelector('[data-chat-group-key]')).toBe(group) expect(group?.hasAttribute('hidden')).toBe(!manualOpen) } - expect(toggle.textContent).toBe(h.props.t('message.turnProcess.took', { duration: formatRunDuration(4_000, h.props.t) })) + expect(toggle.textContent).toBe(h.props.t('message.turnProcess.took') + formatRunDuration(4_000, h.props.t)) act(() => { h.set({ hasMore: false }) }) expect(toggle.getAttribute('aria-expanded')).toBe(String(manualOpen)) }) @@ -2840,7 +2844,7 @@ describe('ChatView', () => { const view = render() const toggle = turnProcessControl(view.container)! const processRow = view.getByText('working').closest('[data-chat-flow-kind="assistant-step"]')! - expect(toggle.textContent).toBe('用时 5秒') + expect(toggle.textContent).toBe('已完成,用时 5秒') expect(toggle.disabled).toBe(true) expect(toggle.getAttribute('aria-expanded')).toBeNull() expect(processRow.hasAttribute('hidden')).toBe(false) @@ -2936,7 +2940,7 @@ describe('ChatView', () => { }) const view = render() // The exact turn/end includes trailing tool activity after the final text. - expect(view.container.querySelector('[data-turn-tail="1"]')?.textContent).not.toContain('用时 19秒') + expect(view.container.querySelector('[data-turn-tail="1"]')?.textContent).not.toContain('已完成,用时 19秒') }) it('the assistant footer omits hour-scale run time', () => { @@ -2952,7 +2956,7 @@ describe('ChatView', () => { }) const view = render() expect(view.container.querySelector('[data-turn-tail="1"]')?.textContent) - .not.toContain('用时 1小时05分03秒') + .not.toContain('已完成,用时 1小时5分3秒') }) it('the settled footer shows usage only in Detailed mode', () => { @@ -2992,7 +2996,7 @@ describe('ChatView', () => { expect(view.queryByRole('dialog')).toBeNull() const footer = view.container.querySelector('[data-turn-tail="1"]')! expect(within(footer).queryByRole('button', { name: /用时/ })).toBeNull() - expect(turnProcessControl(view.container)?.textContent).toBe('用时 19秒') + expect(turnProcessControl(view.container)?.textContent).toBe('已完成,用时 19秒') act(() => { h.setPerformanceUsage('compact') }) expect(view.queryByRole('button', { name: /用量/ })).toBeNull() act(() => { h.setPerformanceUsage('detailed') }) @@ -3013,7 +3017,7 @@ describe('ChatView', () => { const view = render() const footer = view.container.querySelector('[data-turn-tail="1"]')! expect(within(footer).queryByRole('button', { name: /用时/ })).toBeNull() - expect(turnProcessControl(view.container)?.textContent).toBe('用时 19秒') + expect(turnProcessControl(view.container)?.textContent).toBe('已完成,用时 19秒') expect(view.queryByRole('button', { name: /用量/ })).toBeNull() }) @@ -3207,9 +3211,9 @@ describe('ChatView', () => { const view = render() expect(view.getByTestId('tool-seat-r1')).toBeTruthy() expect(h.toolOwners[0]?.block).toMatchObject({ callId: 'r1', argsRaw: '{"command":"cmd-r1"}' }) - expect(view.getByRole('status').textContent).toBe('深度求索中...') + expect(view.getByRole('status').textContent).toBe('深度求索中') expect(turnProcessControl(view.container)).toBeNull() - expect(runningContent(view.container)?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(runningContent(view.container)?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) })) it('keeps the Tool renderer mounted when a running call settles into log order', () => { @@ -3269,9 +3273,9 @@ describe('ChatView', () => { const view = render() const status = view.getByRole('status') const content = runningContent(view.container)! - expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒\.\.\.$/) + expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒 ···$/) expect(turnProcessControl(view.container)).toBeNull() - expect(status.textContent).toBe('深度求索中...') + expect(status.textContent).toBe('深度求索中') expect(status.getAttribute('aria-live')).toBe('polite') expect(status.getAttribute('aria-atomic')).toBe('true') expect(content.closest('[aria-live]')).toBeNull() @@ -3284,23 +3288,23 @@ describe('ChatView', () => { role: 'user', source: { kind: 'user' }, }] } }) }) - expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒\.\.\.$/) + expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒 ···$/) const beforeTick = content.textContent act(() => { vi.advanceTimersByTime(2_000) }) - expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒\.\.\.$/) + expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒 ···$/) expect(content.textContent).not.toBe(beforeTick) expect(view.getByRole('status')).toBe(status) - expect(status.textContent).toBe('深度求索中...') + expect(status.textContent).toBe('深度求索中') act(() => { h.setSession({ testInbox: { 'next-turn': [], 'next-step': [] } }) h.setChat({ nodes: [trigger, { ...steering(2, 'also', 1), time: 128_000 }] }) }) expect(runningContent(view.container)).toBe(content) - expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒\.\.\.$/) + expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒 ···$/) expect(view.getByText('also').closest('[data-pending-steering]')).toBeNull() act(() => { vi.advanceTimersByTime(1_000) }) - expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒\.\.\.$/) - expect(status.textContent).toBe('深度求索中...') + expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒 ···$/) + expect(status.textContent).toBe('深度求索中') view.unmount() expect(vi.getTimerCount()).toBe(0) })) @@ -3314,14 +3318,14 @@ describe('ChatView', () => { ) const view = render() const content = runningContent(view.container)! - expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒\.\.\.$/) + expect(content.textContent).toMatch(/^深度求索中,用时 \d+分\d+秒 ···$/) act(() => { vi.advanceTimersByTime(1_000) }) - expect(content.textContent).toMatch(/^深度求索中,用时 \d+小时\d+分\d+秒\.\.\.$/) + expect(content.textContent).toMatch(/^深度求索中,用时 \d+小时\d+分\d+秒 ···$/) const hourlyText = content.textContent act(() => { vi.advanceTimersByTime(303_000) }) - expect(content.textContent).toMatch(/^深度求索中,用时 \d+小时\d+分\d+秒\.\.\.$/) + expect(content.textContent).toMatch(/^深度求索中,用时 \d+小时\d+分\d+秒 ···$/) expect(content.textContent).not.toBe(hourlyText) - expect(view.getByRole('status').textContent).toBe('深度求索中...') + expect(view.getByRole('status').textContent).toBe('深度求索中') view.unmount() expect(vi.getTimerCount()).toBe(0) })) @@ -4104,8 +4108,8 @@ describe('ChatView', () => { const column = view.container.querySelector('[data-chat-flow]')! const status = column.querySelector('[data-chat-running]') expect(column.lastElementChild).toBe(status) - expect(within(status as HTMLElement).getByRole('status').textContent).toBe('深度求索中...') - expect(status?.lastElementChild?.textContent).toBe('深度求索中...') + expect(within(status as HTMLElement).getByRole('status').textContent).toBe('深度求索中') + expect(status?.lastElementChild?.textContent).toBe('深度求索中') expect(status?.querySelector('svg')?.parentElement?.getAttribute('aria-hidden')).toBe('true') expect(status?.querySelector('animate')).not.toBeNull() act(() => { h.setSession({ running: false }) }) diff --git a/packages/client/ui-chat/tests/running-status.client.spec.tsx b/packages/client/ui-chat/tests/running-status.client.spec.tsx index 58b648e5c1..6813410c56 100644 --- a/packages/client/ui-chat/tests/running-status.client.spec.tsx +++ b/packages/client/ui-chat/tests/running-status.client.spec.tsx @@ -24,14 +24,14 @@ function statusHarness(startTime?: number) { describe('RunningStatus', () => { it('waits for an open Turn start before allocating its clock', () => { const view = statusHarness() - expect(view.content()?.textContent).toBe('深度求索中...') + expect(view.content()?.textContent).toBe('深度求索中') expect(vi.getTimerCount()).toBe(0) view.set(1_000) - expect(view.content()?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(view.content()?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) expect(view.content()?.querySelectorAll('[data-shimmer="true"]')).toHaveLength(1) expect(vi.getTimerCount()).toBe(1) view.set() - expect(view.content()?.textContent).toBe('深度求索中...') + expect(view.content()?.textContent).toBe('深度求索中') expect(vi.getTimerCount()).toBe(0) }) @@ -41,28 +41,28 @@ describe('RunningStatus', () => { const status = view.getByRole('status') const initialText = content?.textContent act(() => { vi.advanceTimersByTime(2_000) }) - expect(content?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(content?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) expect(content?.textContent).not.toBe(initialText) view.set(7_000) expect(view.content()).toBe(content) - expect(content?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(content?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) expect(vi.getTimerCount()).toBe(1) const nextTurnText = content?.textContent act(() => { vi.advanceTimersByTime(2_000) }) - expect(content?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(content?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) expect(content?.textContent).not.toBe(nextTurnText) expect(view.getByRole('status')).toBe(status) - expect(status.textContent).toBe('深度求索中...') + expect(status.textContent).toBe('深度求索中') view.unmount() expect(vi.getTimerCount()).toBe(0) }) it('keeps the duration nonnegative when the start is ahead of the local clock', () => { const view = statusHarness(6_000) - expect(view.content()?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(view.content()?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) expect(view.content()?.textContent).not.toContain('-') act(() => { vi.advanceTimersByTime(3_000) }) - expect(view.content()?.textContent).toMatch(/^深度求索中,用时 \d+秒\.\.\.$/) + expect(view.content()?.textContent).toMatch(/^深度求索中,用时 \d+秒 ···$/) expect(view.content()?.textContent).not.toContain('-') }) diff --git a/packages/client/ui-deliverables/README.i18n.yaml b/packages/client/ui-deliverables/README.i18n.yaml index 21b34b5eef..e9516416e7 100644 --- a/packages/client/ui-deliverables/README.i18n.yaml +++ b/packages/client/ui-deliverables/README.i18n.yaml @@ -15,8 +15,8 @@ en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-client-ui-deliverables/use-this-package: - en: cd0926783147a496 - zh: b9d713c5c19c3aa3 + en: 4a2fcdcf13276fef + zh: 8bde03bbbf15840c /deepseek-ai-dsh-client-ui-deliverables/use-this-package/explicit-deliveries: en: 0ee4bbe45bb5e473 zh: 9c99d829b25f59ba diff --git a/packages/client/ui-deliverables/README.md b/packages/client/ui-deliverables/README.md index b194a9d3b4..8805bbf43e 100644 --- a/packages/client/ui-deliverables/README.md +++ b/packages/client/ui-deliverables/README.md @@ -27,7 +27,7 @@ This package renders the changed-files card a finished turn ends with — the fi Mount this plugin alongside `ui-conversation` and the Host [workspace-changes](../../deliverables/workspace-changes/README.md) plugin; a finished turn then ends with the changed-files card between the closing message's body and its action footer. Without a served summary — a turn that changed no file, the plugin composed out, or a Host restarted since the turn ran — the card is absent and only deliveries and prose links remain; outside a git repository the summary lists file-tool edits only. -Changed-file and delivery entry cards, their file-icon tiles, and hover previews follow the [R16 preview-card rule](../../../docs/ui-radius.md#radius-scale), retaining the same contour on hover. +Changed-file and delivery entry cards and hover previews follow the [R16 preview-card rule](../../../docs/ui-radius.md#radius-scale); their file-icon tiles use the same rule’s R10 exception. Each retains the same contour on hover. ### Explicit deliveries diff --git a/packages/client/ui-deliverables/README.zh.md b/packages/client/ui-deliverables/README.zh.md index 06ce26aebd..493082ef65 100644 --- a/packages/client/ui-deliverables/README.zh.md +++ b/packages/client/ui-deliverables/README.zh.md @@ -27,7 +27,7 @@ kind: "package-reference" 与 `ui-conversation` 和 Host 侧的 [workspace-changes](../../deliverables/workspace-changes/README.zh.md) 插件一起挂载本插件;已完成轮次随即以改动文件卡片收尾,位于收尾消息正文与其动作页脚之间。没有可提供的摘要时——本轮没有改动任何文件、该插件被组合出去,或该轮之后 Host 重启过——卡片不出现,只保留交付卡片与正文链接;工作区不在 git 仓库内时摘要只列文件工具的编辑。 -改动文件与交付文件的入口卡片、内部文件图标底框和悬停预览浮层遵循 [R16 预览卡片规则](../../../docs/ui-radius.zh.md#radius-scale),hover 保持相同轮廓。 +改动文件与交付文件的入口卡片和悬停预览浮层遵循 [R16 预览卡片规则](../../../docs/ui-radius.zh.md#radius-scale);内部文件图标底框采用该规则的 R10 特例。各自的 hover 保持相同轮廓。 ### 显式交付 diff --git a/packages/client/ui-deliverables/src/client/ChangedFiles.module.css b/packages/client/ui-deliverables/src/client/ChangedFiles.module.css index d0675a439d..6ec8bc7581 100644 --- a/packages/client/ui-deliverables/src/client/ChangedFiles.module.css +++ b/packages/client/ui-deliverables/src/client/ChangedFiles.module.css @@ -7,7 +7,7 @@ button.header { cursor: pointer; transition: background-color 120ms ease; } button.header:hover:not(:disabled), button.header:focus-visible { background: var(--changes-hover); } button.header:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); } button.header:disabled { cursor: progress; } -.tile { display: grid; flex: none; place-items: center; box-sizing: border-box; width: 40px; height: 40px; border: 0.5px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-lg); background: color-mix(in srgb, var(--dsw-static-neutral-00) 50%, transparent); } +.tile { display: grid; flex: none; place-items: center; box-sizing: border-box; width: 40px; height: 40px; border: 0.5px solid var(--dsw-alias-border-l1); border-radius: 10px; background: color-mix(in srgb, var(--dsw-static-neutral-00) 50%, transparent); } :global(body[data-ds-dark-theme]) .tile { background: color-mix(in srgb, var(--dsw-static-neutral-00) 5%, transparent); } .titles { display: flex; flex: 1; flex-direction: column; min-width: 0; } .title { overflow: hidden; font-size: 13px; font-weight: 500; line-height: 20px; text-overflow: ellipsis; white-space: nowrap; } diff --git a/packages/client/ui-deliverables/src/client/Deliverables.module.css b/packages/client/ui-deliverables/src/client/Deliverables.module.css index 4e63f270f6..79b5043de1 100644 --- a/packages/client/ui-deliverables/src/client/Deliverables.module.css +++ b/packages/client/ui-deliverables/src/client/Deliverables.module.css @@ -11,7 +11,7 @@ .file:hover { background: var(--deliverable-hover); } .cardPreview { position: absolute; z-index: 1; inset: 0; width: 100%; padding: 0; border: 0; border-radius: inherit; background: transparent; cursor: pointer; } .cardPreview:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); } -.fileIcon { position: relative; z-index: 2; box-sizing: border-box; flex: 0 0 auto; display: grid; place-items: center; width: 40px; height: 40px; overflow: hidden; pointer-events: none; border: 0.5px solid var(--dsw-alias-border-l1); border-radius: var(--dsw-radius-lg); background: color-mix(in srgb, var(--dsw-static-neutral-00) 50%, transparent); color: var(--dsw-alias-link); } +.fileIcon { position: relative; z-index: 2; box-sizing: border-box; flex: 0 0 auto; display: grid; place-items: center; width: 40px; height: 40px; overflow: hidden; pointer-events: none; border: 0.5px solid var(--dsw-alias-border-l1); border-radius: 10px; background: color-mix(in srgb, var(--dsw-static-neutral-00) 50%, transparent); color: var(--dsw-alias-link); } :global(body[data-ds-dark-theme]) .fileIcon { background: color-mix(in srgb, var(--dsw-static-neutral-00) 5%, transparent); } .fileBody { position: relative; z-index: 2; display: flex; flex: 1; align-items: center; justify-content: space-between; gap: 12px; min-width: 0; pointer-events: none; } .details { display: flex; flex: 1; flex-direction: column; justify-content: center; gap: 2px; min-width: 0; } diff --git a/packages/client/ui-deliverables/src/client/PresentRow.module.css b/packages/client/ui-deliverables/src/client/PresentRow.module.css index d9f2dcb2af..55b70927b7 100644 --- a/packages/client/ui-deliverables/src/client/PresentRow.module.css +++ b/packages/client/ui-deliverables/src/client/PresentRow.module.css @@ -1,5 +1,5 @@ /** Present status remains readable beside long workspace paths. */ -.summary { margin-left: 8px; display: flex; align-items: center; gap: 8px; min-width: 0; color: var(--dsw-alias-label-secondary); font-size: 12px; } +.summary { margin-left: 8px; display: flex; align-items: center; gap: 8px; min-width: 0; color: inherit; font-size: 12px; } .summary > :first-child { flex-shrink: 0; } .paths { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .output { margin: 8px 0; padding: 12px; border-radius: var(--dsw-radius-lg); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; } diff --git a/packages/client/ui-primitives/README.i18n.yaml b/packages/client/ui-primitives/README.i18n.yaml index 5a737bad28..a971b9e834 100644 --- a/packages/client/ui-primitives/README.i18n.yaml +++ b/packages/client/ui-primitives/README.i18n.yaml @@ -18,8 +18,8 @@ en: 7bae72c97d0449f5 zh: 4589ecc2b216edc5 /deepseek-ai-dsh-client-ui-primitives/use-this-package/component-catalog: - en: 91d4b79159aab5df - zh: 3a2d8461e836bb66 + en: b521f773eff5c728 + zh: 7b56c5b4777a0b00 /deepseek-ai-dsh-client-ui-primitives/use-this-package/controls-and-icons: en: b4cd33720a6cf6b8 zh: 8db75bf633789c6d diff --git a/packages/client/ui-primitives/README.md b/packages/client/ui-primitives/README.md index 6fdbce8a2a..663832fe07 100644 --- a/packages/client/ui-primitives/README.md +++ b/packages/client/ui-primitives/README.md @@ -52,7 +52,7 @@ Check this table before writing a control in a feature package. A plugin cannot | `PathLabel` | Single-line file path with subdued directories, a primary filename, and the full path on hover. Fitting paths align left; clipped paths preserve their suffix with a left-edge fade that updates on path and size changes. | | `StateDot` | Solid green `done`, amber `warning`, red `error`, and neutral-grey `idle` marks in a 10px slot, plus a tertiary-grey 14px rotating `ongoing` loader whose animations pin to document time zero so every visible loader rotates in phase. `aria-hidden`, so the render site owns the name. `appearance="step"` shows a filled check for completion and a hollow pending circle. | | `ConnectionIndicator` | Inline connection-recovery control across outage, retry, and recovered states. | -| `DisclosureRow` | 24px compact disclosure that lays title and content side by side. Memoized with shallow prop comparison; keep callbacks and React-node props stable when their content is unchanged. | +| `DisclosureRow` | 24px compact disclosure that lays title and content side by side. The header supplies tertiary at rest and secondary on hover; text and icons inherit it regardless of expandability or open state unless the caller supplies a feature-specific override. Semantic status colors remain explicit. Collapsed rows preview a down chevron on hover, and expanded rows keep an up chevron visible. Memoized with shallow prop comparison; keep callbacks and React-node props stable when their content is unchanged. | | `TextShimmer` | One left-to-right highlight across a row’s title, separators, summary, and suffix, with a 300ms initial delay, a one-second sweep and a 500ms rest. The mask spans the content width, capped by the visible row and tilts 15° from vertical, with a flat peak and soft edges. The moving decoration stays clipped to the row without enlarging its scrollable area. Base text inherits the caller’s color, including hover changes; the theme supplies the translucent highlight. Place icons and chevrons outside `TextShimmer` so only text and separators receive the highlight. A string child uses the same retained text interface; nested `TextShimmer` instances share the outer animation. Composite children render twice while active and must have no effects or element ids. Mark separators with `data-shimmer-decoration` so their fill receives the highlight. `DisclosureRow` forwards `contentClassName` for the text area and `contentLayoutClassName` for its inner layout. The inert decorative copy is excluded from interaction, selection, and accessibility; text updates retain the animation, and reduced-motion mode keeps the static base. | | `Modal` | Centered dialog over a page mask. A nested dialog can intercept keys with `onKeyDownCapture` before document Escape handlers. The tint and dialog fade in while backdrop blur stays fully applied, honoring reduced motion. Set `backdropBlur={false}` when the caller already blurs the source page. | | `RiskConfirmation` | Sensitive action gated behind an explicit checkbox. | @@ -65,7 +65,7 @@ Check this table before writing a control in a feature package. A plugin cannot | `JsonTree`, `JsonBlock` | Read-only JSON inspection. | | `MarkdownText`, `MarkdownDelegateProvider`, `CodeBlock` | Untrusted GFM with TeX math, owner-delegated HTTP(S) navigation, and highlighted code. `CodeBlock` accepts opt-in `lineNumbers`; copied source excludes the gutter, and `contentRef` exposes its stable source wrapper to an owner that uses it as a scrollport. Set `showHeader={false}` when the owner supplies its own language and copy toolbar. | | `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, `WebBlock` | The agent-output card matching each tool-result intent. | -| `icons/*`, `FishLogo`, `BrandWordmark`, `ReferenceIconRegular`/`ReferenceIconMedium`, `LinkIconRegular`/`LinkIconMedium` | Glyphs and brand marks. Use `LinkIconMedium` for 14px clickable-link categories and known-site marks. | +| `icons/*`, `FishLogo`, `BrandWordmark`, `ReferenceIconRegular`/`ReferenceIconMedium`, `LinkIconRegular`/`LinkIconMedium` | Glyphs and brand marks. The thinking glyph keeps an inset orbit within its 16px view box. Use `LinkIconMedium` for 14px clickable-link categories and known-site marks. | | `PermissionIconReadOnlyRegular`/`Medium`, `PermissionIconWorkspaceWriteRegular`/`Medium`, `PermissionIconFullAccessRegular`/`Medium` | Permission-mode glyphs for read-only, workspace-write, and full-access choices. | | `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` | Fixed-palette 36×36 plugin artwork; `Terminal` supplies the light-blue prompt in plugin cards and sidebar guide entries. `Default` marks plugins without artwork of their own. Def ids are per-instance, so the same artwork repeats safely on one page. | | `GuideArtworkBrowser`/`Files` | Fixed-palette 36×36 browser and folder artwork for sidebar guide entries. | diff --git a/packages/client/ui-primitives/README.zh.md b/packages/client/ui-primitives/README.zh.md index 029f271387..03b43c609a 100644 --- a/packages/client/ui-primitives/README.zh.md +++ b/packages/client/ui-primitives/README.zh.md @@ -52,7 +52,7 @@ kind: "package-library" | `PathLabel` | 单行文件路径:目录使用弱化颜色,文件名使用主色,悬停可查看完整路径。空间足够时靠左显示;溢出时保留尾部并在左侧渐隐,路径或尺寸变化时更新。 | | `StateDot` | 10px 槽内的绿色 `done`、琥珀色 `warning`、红色 `error`、中性灰色 `idle` 圆点,以及 tertiary 灰色 14px 旋转 `ongoing` loading,其动画固定到文档时间零点,所以所有可见 loading 同相旋转。它是 `aria-hidden` 的,名称由渲染点提供。 `appearance="step"` 以实心勾表示完成、空心圆表示等待。 | | `ConnectionIndicator` | 行内连接恢复控件,覆盖断线、重试与已恢复三种状态。 | -| `DisclosureRow` | 24px 紧凑折叠行,标题与内容左右排列。使用浅层 prop 比较进行 memo;内容未变时,保持回调与 React 节点 prop 的引用稳定。 | +| `DisclosureRow` | 24px 紧凑折叠行,标题与内容左右排列。标题行默认使用 tertiary,悬停时使用 secondary;文字与图标继承该颜色,不受能否展开及开合状态影响,调用方可提供功能专属覆盖。语义状态色保持显式指定。收起时悬停预览向下箭头,展开后持续显示向上箭头。使用浅层 prop 比较进行 memo;内容未变时,保持回调与 React 节点 prop 的引用稳定。 | | `TextShimmer` | 同一道高光从左到右扫过一行的标题、分隔符、摘要与后缀:首次等待 300ms,扫动一秒,再静止 500ms。遮罩覆盖内容宽度,并限制在可见行宽内,相对垂直方向倾斜 15°,峰值区域平坦,两侧柔和渐隐。移动的装饰层始终裁剪在行内,不会扩大可滚动区域。底色继承调用方的颜色,包括悬停变化;主题提供半透明高光。图标和箭头放在 `TextShimmer` 外,只有文字和分隔符参与扫光。字符串子节点沿用稳定的文字接口;嵌套的 `TextShimmer` 共用外层动画。活动期间组合子节点渲染两次,因此不得带副作用或元素 id。分隔符标记 `data-shimmer-decoration`,让填色参与扫光。`DisclosureRow` 将 `contentClassName` 传给文字区域,将 `contentLayoutClassName` 传给内部布局。惰性装饰副本不参与交互、选区和无障碍访问;文字更新保留动画,减少动态效果模式保留静态底色。 | | `Modal` | 页面遮罩之上的居中对话框。嵌套对话框可通过 `onKeyDownCapture` 在文档级 Escape 处理器之前拦截按键。 色层与弹窗淡入,背景模糊始终完整生效,并遵循减少动态效果偏好。调用方已模糊源页面时设置 `backdropBlur={false}`。 | | `RiskConfirmation` | 以显式复选框把关的敏感操作确认。 | @@ -65,7 +65,7 @@ kind: "package-library" | `JsonTree`、`JsonBlock` | 只读 JSON 查看。 | | `MarkdownText`、`MarkdownDelegateProvider`、`CodeBlock` | 不可信 GFM 与 TeX 数学、owner 委托的 HTTP(S) 导航,以及高亮代码。`CodeBlock` 可通过 `lineNumbers` 开启行号;复制的源码不含行号栏,`contentRef` 则向需要把稳定源码包装节点用作滚动区的 owner 提供该节点。调用方提供自己的语言与复制工具栏时,设置 `showHeader={false}`。 | | `TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock`、`WebBlock` | 与各类工具结果意图对应的 agent 输出卡片。 | -| `icons/*`、`FishLogo`、`BrandWordmark`、`ReferenceIconRegular`/`ReferenceIconMedium`、`LinkIconRegular`/`LinkIconMedium` | 字形与品牌标识。`LinkIconMedium` 用于 14px 的可点击链接分类及已知站点标记。 | +| `icons/*`、`FishLogo`、`BrandWordmark`、`ReferenceIconRegular`/`ReferenceIconMedium`、`LinkIconRegular`/`LinkIconMedium` | 字形与品牌标识。思考图标的轨道在 16px 视口内保留内边距。`LinkIconMedium` 用于 14px 的可点击链接分类及已知站点标记。 | | `PermissionIconReadOnlyRegular`/`Medium`、`PermissionIconWorkspaceWriteRegular`/`Medium`、`PermissionIconFullAccessRegular`/`Medium` | 只读、工作区写入与完全访问选项使用的权限模式图形。 | | `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` | 固定配色的 36×36 插件插画;`Terminal` 为插件卡片和侧边栏开始页入口提供浅蓝色提示符。`Default` 用于没有自有插画的插件。def id 按实例生成,同一插画可在一页中安全重复。 | | `GuideArtworkBrowser`/`Files` | 固定配色的 36×36 浏览器与文件夹插画,用于侧栏引导入口。 | diff --git a/packages/client/ui-primitives/src/DisclosureRow.module.css b/packages/client/ui-primitives/src/DisclosureRow.module.css index fed453f345..90fc1f8c2d 100644 --- a/packages/client/ui-primitives/src/DisclosureRow.module.css +++ b/packages/client/ui-primitives/src/DisclosureRow.module.css @@ -20,12 +20,18 @@ align-items: center; height: calc(24px + var(--dsh-content-font-delta, 0px)); min-width: 0; + color: var(--dsw-alias-label-tertiary); + transition: color 100ms ease; } .row[data-expandable] { cursor: pointer; } +.row:hover { + color: var(--dsw-alias-label-secondary); +} + .leading { position: relative; flex: none; @@ -38,7 +44,7 @@ padding: 0; border: none; background: none; - color: var(--dsw-alias-label-tertiary); + color: inherit; } /* Flow-row glyphs render at 14px inside the 16px box; the CSS edge overrides @@ -80,5 +86,11 @@ button.leading { flex: none; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); - color: var(--dsw-alias-label-secondary); + color: inherit; +} + +@media (prefers-reduced-motion: reduce) { + .row { + transition: none; + } } diff --git a/packages/client/ui-primitives/src/DisclosureRow.tsx b/packages/client/ui-primitives/src/DisclosureRow.tsx index c429547ecf..a8360d365b 100644 --- a/packages/client/ui-primitives/src/DisclosureRow.tsx +++ b/packages/client/ui-primitives/src/DisclosureRow.tsx @@ -4,7 +4,7 @@ import { IconChevronDownOutlineRegular, IconChevronUpOutlineRegular } from './ic import { TextShimmer } from './TextShimmer.tsx' import css from './DisclosureRow.module.css' -/** Shared 24px disclosure chrome for compact flow rows. */ +/** Shared 24px process row: tertiary text and icons, secondary on hover. */ export interface DisclosureRowProps { icon: ReactNode title: string diff --git a/packages/client/ui-primitives/src/icons/index.tsx b/packages/client/ui-primitives/src/icons/index.tsx index ce7e15f198..d22cbdd06d 100644 --- a/packages/client/ui-primitives/src/icons/index.tsx +++ b/packages/client/ui-primitives/src/icons/index.tsx @@ -450,9 +450,9 @@ export const IconEditOutlineMedium = (props: IconProps) => ( const IconThinkOutlineArtwork = ({ size = 16, className, strokeWidth }: WeightedIconProps) => ( ) diff --git a/packages/client/ui-settings-general/README.i18n.yaml b/packages/client/ui-settings-general/README.i18n.yaml index 1ce0fa39c1..f8ba0625be 100644 --- a/packages/client/ui-settings-general/README.i18n.yaml +++ b/packages/client/ui-settings-general/README.i18n.yaml @@ -15,8 +15,8 @@ en: 2843d46e95311bce zh: ec11bdeb5d3ce818 /deepseek-ai-dsh-client-ui-settings-general/use-this-package: - en: 3be9bf992c10bc6b - zh: b270083651e5dc43 + en: 8b75b6fc5a0951e3 + zh: 90c54f660988588b /deepseek-ai-dsh-client-ui-settings-general/use-this-package/the-general-section: en: 8b8a7c6287bb303b zh: 19535112531aedd5 diff --git a/packages/client/ui-settings-general/README.md b/packages/client/ui-settings-general/README.md index 4df26c7df0..5a746969fd 100644 --- a/packages/client/ui-settings-general/README.md +++ b/packages/client/ui-settings-general/README.md @@ -35,7 +35,7 @@ When the section navigation exceeds the panel's available height, the list scrol In Desktop, the account-row update control shows availability, progress, verification, readiness, and persistent retry feedback. It shares the connection indicator’s 28px height, 8px corners, 14px icon slot, 4px icon gap, and 12px medium text with an 18px line height; update-state border, fill, and text colors remain independently defined. Retry text and its dot use the same brand blue as other update labels; numeric download progress has no ellipsis. The preload carries semantic phase, version, progress, and classified failures; the component resolves every visible and accessible string from the active `settings` locale, including after an in-application language change. Selecting an available update starts downloading; installation requires a separate shell-owned confirmation. A collapsed sidebar shows the same status as a brand-blue dot on its top expand button, including failures. Connection feedback takes priority except during shell-reported installation, when the expected backend disconnect must not hide update status. Failure restores connection feedback. Both controls share one carrier subscription; browser code cannot choose packages or authorize installation. [Desktop updates](../../../apps/desktop/README.md) owns the release workflow. Checking, verifying, and preparing-to-restart tooltips show only the supplied version; without a version, they retain the state label. Desktop update tooltips preserve the complete version and prerelease suffix without adding v or V; their error summaries match the shell update dialogs. -Settings visibility and section selection live in the shell owner store. The shell supplies the effective Settings binding to the contributed launcher for menu keycaps and `aria-keyshortcuts`; the fallback button uses the same binding for hover and keyboard-focus hints and `aria-keyshortcuts`. The Settings command (`Mod+,` by default) toggles the dialog when Settings is in front or no modal is open; the sidebar control opens the same dialog. The command cannot open or close Settings while the shortcut reference or another modal is in front. Held-key repeats do nothing. Initial focus lands on the selected section in the navigation, or on the title when no sections are available, without drawing a focus outline. Tab and directional navigation retain their visible focus indicators. `Mod+/` can open the shortcut reference above Settings; `Escape` closes the top dialog and restores focus to its invoking control. Return focus omits outlines after shortcut, Escape, or pointer dismissal. +Settings visibility and section selection live in the shell owner store. The shell supplies the effective Settings binding to the contributed launcher for menu keycaps and `aria-keyshortcuts`; the fallback button uses the same binding for hover and keyboard-focus hints and `aria-keyshortcuts`. The Settings command (Desktop: `Mod+,`; Windows/macOS Web: `Mod+Alt+,`) toggles the dialog when Settings is in front or no modal is open; the sidebar control opens the same dialog. The command cannot open or close Settings while the shortcut reference or another modal is in front. Held-key repeats do nothing. Initial focus lands on the selected section in the navigation, or on the title when no sections are available, without drawing a focus outline. Tab and directional navigation retain their visible focus indicators. `Mod+/` can open the shortcut reference above Settings; `Escape` closes the top dialog and restores focus to its invoking control. Return focus omits outlines after shortcut, Escape, or pointer dismissal. ### The General section diff --git a/packages/client/ui-settings-general/README.zh.md b/packages/client/ui-settings-general/README.zh.md index dcbba9257d..acca362cdc 100644 --- a/packages/client/ui-settings-general/README.zh.md +++ b/packages/client/ui-settings-general/README.zh.md @@ -35,7 +35,7 @@ kind: "package-reference" 在 Desktop 中,账户行更新控件显示可用版本、进度、验证、就绪和持久重试反馈。它与连接指示器共用 28px 高度、8px 圆角、14px 图标占位、4px 图文间距,以及 12px 中等字重文字和 18px 行高;更新状态的边框、填充和文字颜色独立定义。重试文字及其圆点与其他更新文案使用相同的品牌蓝色;数字下载进度不带省略号。预加载仅传递语义化阶段、版本、进度和失败类别;组件从当前 `settings` locale 解析全部可见与无障碍文案,并在应用内语言变化后同步更新。选择可用更新即开始下载;安装需要独立的壳拥有的确认。侧栏收起时,其顶部展开按钮以品牌蓝色圆点显示相同状态,包括失败状态。连接反馈通常优先展示,但壳报告正在安装时,预期的后端断开不能遮盖更新状态;失败后恢复连接反馈。两个控件共用一次载体订阅;浏览器代码不能选择安装包或授权安装。[Desktop 更新](../../../apps/desktop/README.zh.md)负责发布流程。检查中、校验中和准备重启状态的悬浮提示只显示已提供的版本号;没有版本号时保留状态文案。桌面更新提示完整保留版本号及预发布后缀,不额外添加 v 或 V;错误提示与桌面升级弹窗保持一致。 -设置的可见状态和分区选择由 shell owner store 保存。外壳将设置命令的有效绑定传给入口贡献,用于菜单键帽和 `aria-keyshortcuts`;回退按钮使用相同绑定提供悬停、键盘聚焦提示和 `aria-keyshortcuts`。设置位于最前方或没有模态弹窗时,设置命令(默认为 `Mod+,`)切换设置弹窗的开关;侧栏控件打开同一弹窗。快捷键速查或其他模态弹窗位于前方时,该命令不能打开或关闭设置。长按产生的重复按键不执行操作。初始焦点落在导航中选中的分区;没有可用分区时落在标题上,均不绘制焦点外轮廓线。Tab 和方向键导航保留可见焦点提示。`Mod+/` 可在设置上方打开快捷键速查;`Escape` 关闭最上层弹窗,焦点返回触发该弹窗的控件。通过快捷键、Esc 或鼠标关闭后的回焦均不绘制外轮廓线。 +设置的可见状态和分区选择由 shell owner store 保存。外壳将设置命令的有效绑定传给入口贡献,用于菜单键帽和 `aria-keyshortcuts`;回退按钮使用相同绑定提供悬停、键盘聚焦提示和 `aria-keyshortcuts`。设置位于最前方或没有模态弹窗时,设置命令(Desktop 为 `Mod+,`,Windows/macOS Web 为 `Mod+Alt+,`)切换设置弹窗的开关;侧栏控件打开同一弹窗。快捷键速查或其他模态弹窗位于前方时,该命令不能打开或关闭设置。长按产生的重复按键不执行操作。初始焦点落在导航中选中的分区;没有可用分区时落在标题上,均不绘制焦点外轮廓线。Tab 和方向键导航保留可见焦点提示。`Mod+/` 可在设置上方打开快捷键速查;`Escape` 关闭最上层弹窗,焦点返回触发该弹窗的控件。通过快捷键、Esc 或鼠标关闭后的回焦均不绘制外轮廓线。 ### 「通用」分区 diff --git a/packages/client/ui-settings-general/src/client/index.ts b/packages/client/ui-settings-general/src/client/index.ts index 08e0802b98..a17ea2c519 100644 --- a/packages/client/ui-settings-general/src/client/index.ts +++ b/packages/client/ui-settings-general/src/client/index.ts @@ -182,8 +182,8 @@ export function apply(ctx: ClientContext): void { 'desktop:macos': { code: 'Comma', modifiers: ['primary'] }, 'desktop:windows': { code: 'Comma', modifiers: ['primary'] }, 'desktop:linux': { code: 'Comma', modifiers: ['primary'] }, - 'web:macos': { code: 'Comma', modifiers: ['primary'] }, - 'web:windows': { code: 'Comma', modifiers: ['primary'] }, + 'web:macos': { code: 'Comma', modifiers: ['primary', 'alt'] }, + 'web:windows': { code: 'Comma', modifiers: ['primary', 'alt'] }, }, regions: ['page', 'editable', 'terminal'], modals: ['settings'], resolve: ({ modal }) => { diff --git a/packages/client/ui-shortcuts/README.i18n.yaml b/packages/client/ui-shortcuts/README.i18n.yaml index c9db791ce2..aec20e18ca 100644 --- a/packages/client/ui-shortcuts/README.i18n.yaml +++ b/packages/client/ui-shortcuts/README.i18n.yaml @@ -15,8 +15,8 @@ en: d152484eb41ac6b4 zh: 0cd57b7b8d8c9b59 /deepseek-ai-dsh-client-ui-shortcuts/use-this-package: - en: 2d35beb03b54d4ea - zh: 12f3c6ec1a79c673 + en: 045d38d5efdf1ff0 + zh: 86a700339f5abc49 /deepseek-ai-dsh-client-ui-shortcuts/understand-the-implementation: en: f56f2610e7258825 zh: 249d7cdd55c57166 diff --git a/packages/client/ui-shortcuts/README.md b/packages/client/ui-shortcuts/README.md index 743c948a08..aa2e32e7a7 100644 --- a/packages/client/ui-shortcuts/README.md +++ b/packages/client/ui-shortcuts/README.md @@ -25,7 +25,7 @@ Browse the commands available in the current window and find them by action, Eng ## Use this package -The Web app bundle mounts this package together with `shortcuts`. The reference shows only registered commands and existing fixed input actions. Desktop shows the effective device keys; commands without a Web default show No shortcut. The Settings entry and Mod+/ open the same single dialog, titled “Keyboard shortcuts” in English and “快捷键” in Chinese. Its 480×600 CSS-pixel card shrinks to fit smaller windows; the header and search remain above an internally scrolling list. The search field receives focus on open without changing its border color. Automatic entry and return focus in the reference, recorder, and reset confirmation omit outlines, including returns after Escape and application close shortcuts. Containers remain outline-free, and Tab and directional navigation retain their visible focus indicators. The Settings entry is labeled “Edit shortcuts”, with an “Open from anywhere” shortcut tooltip, and uses a filled button with the same standard radius as adjacent settings controls and no dropdown icon and reads the same effective binding for its tooltip and `aria-keyshortcuts`; both are absent without a binding. +The Web app bundle mounts this package together with `shortcuts`. The reference shows only registered commands and existing fixed input actions. Desktop shows the effective device keys; commands without a Web default show No shortcut. The Settings entry and Mod+/ open the same single dialog, titled “Keyboard shortcuts” in English and “快捷键” in Chinese. Its 480×600 CSS-pixel card shrinks to fit smaller windows; the header and search remain above an internally scrolling list. The search field receives focus on open without changing its border color. Automatic entry and return focus in the reference, recorder, and reset confirmation omit outlines, including returns after Escape and application close shortcuts. Containers remain outline-free, and Tab and directional navigation retain their visible focus indicators. The Settings entry follows Coding Tools and is labeled “Edit shortcuts”, with an “Open from anywhere” shortcut tooltip, and uses a filled button with the same standard radius as adjacent settings controls and no dropdown icon and reads the same effective binding for its tooltip and `aria-keyshortcuts`; both are absent without a binding. `Mod+/` opens the reference when no modal is open or Settings is in front, and closes it when the reference is in front. While the reference is in front, the Settings command cannot open or close Settings. Held-key repeats are ignored. Recording, pending writes, and a nested confirmation prevent the reference shortcut from closing the dialog. diff --git a/packages/client/ui-shortcuts/README.zh.md b/packages/client/ui-shortcuts/README.zh.md index 9dc12ca5ae..5226b14e9f 100644 --- a/packages/client/ui-shortcuts/README.zh.md +++ b/packages/client/ui-shortcuts/README.zh.md @@ -25,7 +25,7 @@ kind: "package-reference" ## 使用此包 -Web 应用 bundle 将此包与 `shortcuts` 一同挂载。速查仅展示已注册命令和现有固定输入操作。桌面端展示设备对应的有效键位;没有 Web 默认键位的命令显示“暂无快捷键”。设置入口与 Mod+/打开同一个单实例弹窗,中文标题为“快捷键”,英文标题为“Keyboard shortcuts”。卡片尺寸为 480×600 CSS 像素,小窗口中缩小以保持可见;标题和搜索框位于内部滚动列表上方。打开时聚焦搜索框,边框颜色保持不变。速查窗、录键框及重置确认框内的自动进入和回焦均不绘制外轮廓线,包括通过 Esc 和应用关闭快捷键触发的回焦。容器仍不绘制焦点外框,Tab 和方向键导航保留可见焦点提示。设置入口标为“编辑快捷键”,快捷键提示文案为“全局唤起查看”,使用与相邻设置控件一致的标准圆角填充按钮,不带下拉图标,并从同一有效绑定读取提示及 `aria-keyshortcuts`;没有绑定时不显示提示,也不设置该属性。 +Web 应用 bundle 将此包与 `shortcuts` 一同挂载。速查仅展示已注册命令和现有固定输入操作。桌面端展示设备对应的有效键位;没有 Web 默认键位的命令显示“暂无快捷键”。设置入口与 Mod+/打开同一个单实例弹窗,中文标题为“快捷键”,英文标题为“Keyboard shortcuts”。卡片尺寸为 480×600 CSS 像素,小窗口中缩小以保持可见;标题和搜索框位于内部滚动列表上方。打开时聚焦搜索框,边框颜色保持不变。速查窗、录键框及重置确认框内的自动进入和回焦均不绘制外轮廓线,包括通过 Esc 和应用关闭快捷键触发的回焦。容器仍不绘制焦点外框,Tab 和方向键导航保留可见焦点提示。设置入口位于代码工作工具之后,标为“编辑快捷键”,快捷键提示文案为“全局唤起查看”,使用与相邻设置控件一致的标准圆角填充按钮,不带下拉图标,并从同一有效绑定读取提示及 `aria-keyshortcuts`;没有绑定时不显示提示,也不设置该属性。 没有模态弹窗或设置位于最前方时,`Mod+/` 打开速查;速查位于最前方时,该快捷键关闭速查。速查位于前方时,设置命令不能打开或关闭设置。长按产生的重复按键不执行操作。录键期间、写入尚未完成或嵌套确认框打开时,速查快捷键不会关闭弹窗。 diff --git a/packages/client/ui-shortcuts/src/client/index.ts b/packages/client/ui-shortcuts/src/client/index.ts index d0da0c03ba..9aa3547ae2 100644 --- a/packages/client/ui-shortcuts/src/client/index.ts +++ b/packages/client/ui-shortcuts/src/client/index.ts @@ -40,7 +40,7 @@ export function apply(ctx: Context): void { const injected = () => ({ platform: ctx.shortcuts.platform, runtime: ctx.shortcuts.runtime, edit, recording, describeBinding, hooks: { catalog: ctx.shortcuts.catalog, config: ctx.shortcuts.config, fixedCatalog: ctx.shortcuts.fixedCatalog } }) ctx.slots.inject('settings.general.item', () => ctx.slots.register({ - name: 'settings.general.item', id: 'shortcuts', order: 20, locale: 'shortcuts', store, + name: 'settings.general.item', id: 'shortcuts', order: 16, locale: 'shortcuts', store, inject: injected, }, ShortcutsRow)) ctx.slots.inject('shell.overlay', () => { diff --git a/packages/client/ui-sidebar/README.i18n.yaml b/packages/client/ui-sidebar/README.i18n.yaml index 46a8755740..b1ffa3bf3e 100644 --- a/packages/client/ui-sidebar/README.i18n.yaml +++ b/packages/client/ui-sidebar/README.i18n.yaml @@ -18,8 +18,8 @@ en: a970f0547496634f zh: 36ef9a85b26e19f1 /deepseek-ai-dsh-client-ui-sidebar/use-this-package/brand-and-new-session: - en: d084cd6732966c41 - zh: 466e028ca49b4230 + en: 098f85b9287d3147 + zh: b921c128ac37d2cd /deepseek-ai-dsh-client-ui-sidebar/use-this-package/global-panel-entries: en: 5d3300d3506254c5 zh: 541ae1ee1e10b921 diff --git a/packages/client/ui-sidebar/README.md b/packages/client/ui-sidebar/README.md index a6179cfad8..0314df8089 100644 --- a/packages/client/ui-sidebar/README.md +++ b/packages/client/ui-sidebar/README.md @@ -31,7 +31,7 @@ The sidebar is the navigation shell: users see the brand, start new sessions, co ### Brand and New Session -The expanded brand row renders `sidebar.brand.mark` and `sidebar.brand.name` as independent single slots; the collapsed rail renders the same mark slot. Without occupants, the shell uses the fish mark and a localized local-build label. A complete build stacks a code badge below the label as `version[-commit][-dirty]`, using `DSH_CLIENT_VERSION`, the optional 7-character `DSH_CLIENT_COMMIT_HASH`, and `DSH_CLIENT_GIT_DIRTY=true`; missing version metadata omits the badge. New Session targets the explicit Workspace used by a scoped action, otherwise the current Session's Workspace, otherwise the most recently active Workspace; when none exists it clears into the blank New Session page. The expanded New Session button displays the effective binding as trailing grey text on hover or keyboard focus. While the shortcut is visible, the centered icon and label fade before it when space is narrow; CSS reserves the shortcut’s width without measuring the button. Icon-only controls retain tooltips with platform-formatted keycaps, including the resident macOS header control when the sidebar is hidden. All controls expose `aria-keyshortcuts`. +The expanded brand row renders `sidebar.brand.mark` and `sidebar.brand.name` as independent single slots; the collapsed rail renders the same mark slot. On Web and Windows desktop, clicking the expanded brand starts a new session; hovering or focusing it shows no tooltip. Without occupants, the shell uses the fish mark and a localized local-build label. A complete build stacks a code badge below the label as `version[-commit][-dirty]`, using `DSH_CLIENT_VERSION`, the optional 7-character `DSH_CLIENT_COMMIT_HASH`, and `DSH_CLIENT_GIT_DIRTY=true`; missing version metadata omits the badge. New Session targets the explicit Workspace used by a scoped action, otherwise the current Session's Workspace, otherwise the most recently active Workspace; when none exists it clears into the blank New Session page. The expanded New Session button displays the effective binding as trailing grey text on hover or keyboard focus. While the shortcut is visible, the centered icon and label fade before it when space is narrow; CSS reserves the shortcut’s width without measuring the button. Icon-only controls retain tooltips with platform-formatted keycaps, including the resident macOS header control when the sidebar is hidden. All controls expose `aria-keyshortcuts`. ### Global panel entries diff --git a/packages/client/ui-sidebar/README.zh.md b/packages/client/ui-sidebar/README.zh.md index d941f983a4..3255915dcb 100644 --- a/packages/client/ui-sidebar/README.zh.md +++ b/packages/client/ui-sidebar/README.zh.md @@ -31,7 +31,7 @@ dsh Web 客户端的侧边栏让用户识别当前构建、启动新会话、将 ### 品牌与 New Session -展开的品牌行把 `sidebar.brand.mark` 与 `sidebar.brand.name` 渲染为两个独立的 single slot;收起轨道则渲染同一个 mark slot。没有占位者时,外壳使用鱼形标记和本地化的本地构建标签。完整构建会在标签下方显示代码徽标;该徽标使用 `DSH_CLIENT_VERSION`、可选的 7 位 `DSH_CLIENT_COMMIT_HASH` 与 `DSH_CLIENT_GIT_DIRTY=true` 组装成 `version[-commit][-dirty]`;缺少版本元数据时不显示徽标。New Session 优先使用作用域操作明确指定的 Workspace,否则使用当前 Session 所属 Workspace,再否则使用最近活跃 Workspace;一个 Workspace 都没有时则清空选择,进入空白 New Session 页面。 展开态的新建会话按钮在悬停或键盘聚焦时于右侧以灰色文字显示当前有效绑定。快捷键可见且空间较窄时,居中的图标与文字在快捷键之前渐隐;CSS 为快捷键保留宽度,无需测量按钮。纯图标控件保留使用平台键位样式的 tooltip,侧栏隐藏时常驻的 macOS 头部控件也保持一致。所有控件均提供 `aria-keyshortcuts`。 +展开的品牌行把 `sidebar.brand.mark` 与 `sidebar.brand.name` 渲染为两个独立的 single slot;收起轨道则渲染同一个 mark slot。在 Web 和 Windows 桌面端,点击展开态的品牌会新建会话,悬停或聚焦时不显示 tooltip。没有占位者时,外壳使用鱼形标记和本地化的本地构建标签。完整构建会在标签下方显示代码徽标;该徽标使用 `DSH_CLIENT_VERSION`、可选的 7 位 `DSH_CLIENT_COMMIT_HASH` 与 `DSH_CLIENT_GIT_DIRTY=true` 组装成 `version[-commit][-dirty]`;缺少版本元数据时不显示徽标。New Session 优先使用作用域操作明确指定的 Workspace,否则使用当前 Session 所属 Workspace,再否则使用最近活跃 Workspace;一个 Workspace 都没有时则清空选择,进入空白 New Session 页面。 展开态的新建会话按钮在悬停或键盘聚焦时于右侧以灰色文字显示当前有效绑定。快捷键可见且空间较窄时,居中的图标与文字在快捷键之前渐隐;CSS 为快捷键保留宽度,无需测量按钮。纯图标控件保留使用平台键位样式的 tooltip,侧栏隐藏时常驻的 macOS 头部控件也保持一致。所有控件均提供 `aria-keyshortcuts`。 ### 全局面板入口 diff --git a/packages/client/ui-sidebar/src/client/SidebarRoot.tsx b/packages/client/ui-sidebar/src/client/SidebarRoot.tsx index fed580320c..46c019305f 100644 --- a/packages/client/ui-sidebar/src/client/SidebarRoot.tsx +++ b/packages/client/ui-sidebar/src/client/SidebarRoot.tsx @@ -241,17 +241,15 @@ export function SidebarRoot({ return darwinDesktop ? {identity} : ( - - - + ) })()} {!darwinDesktop && toggle} diff --git a/packages/client/ui-skill/README.i18n.yaml b/packages/client/ui-skill/README.i18n.yaml index 86d3394580..a02c562096 100644 --- a/packages/client/ui-skill/README.i18n.yaml +++ b/packages/client/ui-skill/README.i18n.yaml @@ -21,8 +21,8 @@ en: 2ba1a6158c85b46e zh: 9ab58ceef8743849 /deepseek-ai-dsh-client-ui-skill/use-this-package/the-skill-tool-row: - en: 452686249f73702d - zh: 47b0a191dfacba30 + en: 975c6cd80af3684a + zh: 395c10e51c6a15f6 /deepseek-ai-dsh-client-ui-skill/understand-the-implementation: en: df2a0ee9d2638f41 zh: 3b4af7e505e990ca diff --git a/packages/client/ui-skill/README.md b/packages/client/ui-skill/README.md index 2789896035..ad93c06a1a 100644 --- a/packages/client/ui-skill/README.md +++ b/packages/client/ui-skill/README.md @@ -35,7 +35,7 @@ Ordinary-session candidates come from the `skills/list` Remote; the host serves The `preparing` stage shows only the skill icon and title, with no requested skill name or expansion. The `start` and `result` stages use the recorded arguments and output described below; preparation does not consult the installed catalog. -A collapsed row renders the skill glyph, `Skill` title, and requested skill name on the same secondary font-size axis, line height, row height, and glyph scaling as ordinary Tool rows. Every lifecycle state retains the ordinary skill glyph; running calls carry one left-to-right highlight across the title, separator, and skill name, while failures replace the name with the first error line and interruptions retain their explicit status text. A settled row expands into a bounded `Instructions` card containing the exact durable tool output, with the standard trajectory `Inspect` affordance when available; this expanded card keeps its own typography. The row derives its name, lifecycle, and body only from the frozen call/result slice supplied by ui-tool, never from the current catalog, so replay stays stable when installed skills or their descriptions change. +A collapsed row renders the skill glyph, `Skill` title, and requested skill name on the same secondary font-size axis, line height, row height, and glyph scaling as ordinary Tool rows. Every lifecycle state retains the ordinary skill glyph; running calls carry one left-to-right highlight across the title, separator, and skill name, while failures replace the name with the first error line and interruptions retain their explicit status text. A settled row expands into a bounded `Instructions` card containing the exact durable tool output, with the standard trajectory `Inspect` affordance when available; this expanded card keeps its own typography. The row derives its name, lifecycle, and body only from the frozen call/result slice supplied by ui-tool, never from the current catalog, so replay stays stable when installed skills or their descriptions change. Text and icons are tertiary at rest and secondary on hover; error and interruption text retains its semantic color. Hovering over `/name` highlights the entire reference. Clicking a known skill opens its provider-supplied `SKILL.md` path in the right Sidebar while keeping the token editable. An uncached click shares the per-Session catalog fetch and opens when it completes, retaining the clicked Session address. Preset changes, connection resets, and plugin disposal cancel pending previews; a later click fetches the current catalog again. Skills without a file path remain invocable but have no file preview. diff --git a/packages/client/ui-skill/README.zh.md b/packages/client/ui-skill/README.zh.md index 4568c0cacc..69ba568ea8 100644 --- a/packages/client/ui-skill/README.zh.md +++ b/packages/client/ui-skill/README.zh.md @@ -35,7 +35,7 @@ kind: "package-reference" `preparing` 阶段只显示技能图标与标题,不显示请求加载的技能名称,也不能展开。`start` 和 `result` 阶段使用下述记录参数与输出;准备阶段不查询已安装目录。 -收起的行显示 skill 图标、`Skill` 标题与请求加载的 skill 名称,并与普通工具行使用相同的 secondary 字号轴、行高、行高度和图标缩放。所有生命周期状态都保留普通 skill 图标;运行中的调用以同一道高光从左到右扫过标题、分隔符与 skill 名称,失败时以错误首行替换名称,中断则保留明确状态文本。已结算的行展开为一个尺寸受限的 `Instructions` 卡片,其中原样呈现持久化的工具输出;可用时还会提供标准轨迹的 `Inspect` 入口,展开卡片保留自身排版。该行的名称、生命周期与正文只派生自 ui-tool 提供的冻结调用/结果切片,绝不读取当前目录,因此即使已安装的 skill 或其描述发生变化,回放仍保持稳定。 +收起的行显示 skill 图标、`Skill` 标题与请求加载的 skill 名称,并与普通工具行使用相同的 secondary 字号轴、行高、行高度和图标缩放。所有生命周期状态都保留普通 skill 图标;运行中的调用以同一道高光从左到右扫过标题、分隔符与 skill 名称,失败时以错误首行替换名称,中断则保留明确状态文本。已结算的行展开为一个尺寸受限的 `Instructions` 卡片,其中原样呈现持久化的工具输出;可用时还会提供标准轨迹的 `Inspect` 入口,展开卡片保留自身排版。该行的名称、生命周期与正文只派生自 ui-tool 提供的冻结调用/结果切片,绝不读取当前目录,因此即使已安装的 skill 或其描述发生变化,回放仍保持稳定。 文字和图标默认使用 tertiary 色,悬停使用 secondary 色;错误和中断文字保留各自的语义色。 鼠标移入 `/name` 时,背景覆盖整个引用。点击已知 skill 会在右侧栏打开提供方给出的 `SKILL.md` 路径,同时保留文本的可编辑性。缓存未就绪时,点击复用该 Session 的目录请求,在完成后按点击时的 Session 地址打开预览。切换预设、重置连接和插件释放会取消待处理的预览;后续点击重新获取当前目录。没有文件路径的 skill 仍可调用,但没有文件预览。 diff --git a/packages/client/ui-skill/src/client/SkillRow.module.css b/packages/client/ui-skill/src/client/SkillRow.module.css index 1e3c502688..8c74cf6763 100644 --- a/packages/client/ui-skill/src/client/SkillRow.module.css +++ b/packages/client/ui-skill/src/client/SkillRow.module.css @@ -12,6 +12,12 @@ align-items: center; height: calc(24px + var(--dsh-content-font-delta, 0px)); min-width: 0; + color: var(--dsw-alias-label-tertiary); + transition: color 100ms ease; +} + +.row:hover { + color: var(--dsw-alias-label-secondary); } .row[data-expandable] { @@ -27,7 +33,7 @@ align-items: center; justify-content: center; margin-right: 6px; - color: var(--dsw-alias-label-tertiary); + color: inherit; } .leading svg { @@ -36,7 +42,7 @@ } .chevron { - color: var(--dsw-alias-label-secondary); + color: inherit; } .iconIdle { @@ -65,7 +71,6 @@ flex: none; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); - color: var(--dsw-alias-label-secondary); } .separator { @@ -85,7 +90,6 @@ white-space: nowrap; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); - color: var(--dsw-alias-label-tertiary); } .errorSummary { @@ -189,6 +193,7 @@ } @media (prefers-reduced-motion: reduce) { + .row, .iconIdle, .chevronHover, .inspectButton { diff --git a/packages/client/ui-theme/README.i18n.yaml b/packages/client/ui-theme/README.i18n.yaml index 82ac586927..f9b699e924 100644 --- a/packages/client/ui-theme/README.i18n.yaml +++ b/packages/client/ui-theme/README.i18n.yaml @@ -9,8 +9,8 @@ en: 06c7d7624947a72a zh: ac4ca82984564825 /deepseek-ai-dsh-client-ui-theme/summary: - en: c2a579fd56c0c4e2 - zh: f1842fbb209f876c + en: c98899d108fd8318 + zh: ae2103e46d723784 /deepseek-ai-dsh-client-ui-theme/table-of-contents: en: d152484eb41ac6b4 zh: 09388d293f9be9cb @@ -18,8 +18,8 @@ en: 5d64b3d918d5edfa zh: fc929354a0faf33a /deepseek-ai-dsh-client-ui-theme/use-this-package/appearance-and-font-size: - en: 0b0177e1afefe4a7 - zh: 52d88f12a8369ee8 + en: ec98904b19d1b12b + zh: 59d5823ddb168195 /deepseek-ai-dsh-client-ui-theme/use-this-package/registering-a-theme: en: 2cd32ccaba33b3e9 zh: 53ea78d19929ea02 @@ -30,8 +30,8 @@ en: 8fbfdb050e880f9b zh: dd201f049036c5c4 /deepseek-ai-dsh-client-ui-theme/understand-the-implementation/stylesheets: - en: 76374ee500418cfa - zh: 741256fbc54c8207 + en: 4bbf1beae9e25e9c + zh: 6494ac4d34b1fabb /deepseek-ai-dsh-client-ui-theme/understand-the-implementation/scrollbar-rebinding: en: bf76e7c34a331595 zh: 5accd0260ef7c61e diff --git a/packages/client/ui-theme/README.md b/packages/client/ui-theme/README.md index 0dc68a7b41..931f24ea2c 100644 --- a/packages/client/ui-theme/README.md +++ b/packages/client/ui-theme/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-client-ui-theme` lets Web GUI users choose `light`, `dark`, or `system` and set conversation content text from 12 to 17 px in Settings. A loopback client stores both values in the `ui-theme` settings namespace, which the local provider persists in `$DSH_HOME/cordis.patch.yml` by default. The plugin resolves `system` through `prefers-color-scheme` and publishes immutable `ThemeSnapshot`s; ui-layout applies each snapshot to the document. The package also ships the `--dsw-*` token stylesheets and injects a synchronous bootstrap so the selected palette and font size apply before the shell loads. Third-party themes can register alias-token overrides through `ctx.theme`. +`dsh-client-ui-theme` lets Web GUI users choose `light`, `dark`, or `system` and set conversation content text from 10 to 22 px in Settings. A loopback client stores both values in the `ui-theme` settings namespace, which the local provider persists in `$DSH_HOME/cordis.patch.yml` by default. The plugin resolves `system` through `prefers-color-scheme` and publishes immutable `ThemeSnapshot`s; ui-layout applies each snapshot to the document. The package also ships the `--dsw-*` token stylesheets and injects a synchronous bootstrap so the selected palette and font size apply before the shell loads. Third-party themes can register alias-token overrides through `ctx.theme`. ## Table of Contents @@ -29,7 +29,7 @@ Users switch the color scheme and content font size from two rows in Settings (G ### Appearance and font size -The plugin registers Appearance preference cubes and a font-size stepper in the General section. The stepper accepts integer values from 12 through 17 px and defaults to 14 px. It changes conversation headings and base text by the same increment, including the user bubble and composer draft; flow-row titles, summaries, and tables follow one step under the body size, while small text and code keep fixed sizes. Each accepted change writes through the Host settings API. Rapid changes serialize in gesture order with namespace revisions, and a rejected latest write reloads the durable values. Non-loopback pages keep both choices process-local. +The plugin registers Appearance preference cubes and a font-size stepper in the General section. The stepper accepts integer values from 10 to 22 px and defaults to 14 px. It changes conversation headings and base text by the same increment, including the user bubble and composer draft; flow-row titles, summaries, and tables follow one step under the body size, while small text and code keep fixed sizes. Each accepted change writes through the Host settings API. Rapid changes serialize in gesture order with namespace revisions, and a rejected latest write reloads the durable values. Non-loopback pages keep both choices process-local. ### Registering a theme @@ -69,6 +69,8 @@ System toasts use `--dsw-alias-toast-bg` and `--dsw-alias-toast-label` for a sha `--dsw-alias-label-shimmer` supplies an overlay for the shared text shimmer: black at 30% alpha in the light palette and white at 45% alpha in the dark palette. `--dsw-alias-label-deep-diving` and `--dsw-alias-label-deep-diving-shimmer` supply the blue activity label and sweep; the dark palette uses a lighter, less saturated label with a brighter blue sweep. +The `--dsw-alias-turn-trigger-*` tokens provide separate resting and hover backgrounds for Turn-trigger notices in each palette. Dark notices use brighter interactive layers so the resting card remains distinct from the transcript background. + `brand-font.css` exports the local Montserrat Light, Regular and Medium faces (normal style, weights 300, 400 and 500), with `montserrat-light.woff2`, `montserrat-regular.woff2`, `montserrat-medium.woff2` and its SIL Open Font License in `lib/styles/`. Desktop bundles the same stylesheet, font and license for offline welcome brand text; ordinary UI keeps its system font stack. `corner-shape.css` smooths every rounded corner: inside `@supports (corner-shape: superellipse(1.5))` it defines `--dsw-corner-shape` and applies it to all elements and their `::before`/`::after` through the universal selector, so engines without `corner-shape` keep circular corners. Full-round shapes — `border-radius: 50%` circles and pill radii — pair `corner-shape: round` with their radius in the owning component sheet because a superellipse deforms them; the corner-shape stylesheet spec enforces that pairing across every package stylesheet. diff --git a/packages/client/ui-theme/README.zh.md b/packages/client/ui-theme/README.zh.md index 5767eebced..f21c6cc5d2 100644 --- a/packages/client/ui-theme/README.zh.md +++ b/packages/client/ui-theme/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -`dsh-client-ui-theme` 让 Web GUI 用户在设置中选择 `light`、`dark` 或 `system`,并把会话正文字号设为 12 至 17 px。回环客户端把两个值存入 `ui-theme` 设置命名空间,本地提供方默认将其持久化到 `$DSH_HOME/cordis.patch.yml`。插件通过 `prefers-color-scheme` 解析 `system` 并发布不可变的 `ThemeSnapshot`;ui-layout 把每份快照应用到文档。本包还提供 `--dsw-*` token 样式表,并注入同步引导,使所选调色板与字号在外壳加载前生效。第三方主题可通过 `ctx.theme` 注册别名 token 覆盖。 +`dsh-client-ui-theme` 让 Web GUI 用户在设置中选择 `light`、`dark` 或 `system`,并把会话正文字号设为 10 至 22 px。回环客户端把两个值存入 `ui-theme` 设置命名空间,本地提供方默认将其持久化到 `$DSH_HOME/cordis.patch.yml`。插件通过 `prefers-color-scheme` 解析 `system` 并发布不可变的 `ThemeSnapshot`;ui-layout 把每份快照应用到文档。本包还提供 `--dsw-*` token 样式表,并注入同步引导,使所选调色板与字号在外壳加载前生效。第三方主题可通过 `ctx.theme` 注册别名 token 覆盖。 ## 目录 @@ -29,7 +29,7 @@ kind: "package-reference" ### 外观与字号 -插件在「通用」分区注册外观偏好方块与字号步进器。步进器接受 12 至 17 px 的整数,默认值为 14 px。它以相同增量调整会话标题与基础文本,包括用户气泡与 composer 草稿;流内行的标题、摘要与表格跟随比正文低一档的字号,小号文本和代码保持固定字号。每次通过的变更都经 Host settings API 写入。连续快速变更按操作顺序携带命名空间 revision 串行写入,最新写入被拒时重新加载持久值。非 loopback 页面把两个选择都保留在进程内。 +插件在「通用」分区注册外观偏好方块与字号步进器。步进器接受 10 至 22 px 的整数,默认值为 14 px。它以相同增量调整会话标题与基础文本,包括用户气泡与 composer 草稿;流内行的标题、摘要与表格跟随比正文低一档的字号,小号文本和代码保持固定字号。每次通过的变更都经 Host settings API 写入。连续快速变更按操作顺序携带命名空间 revision 串行写入,最新写入被拒时重新加载持久值。非 loopback 页面把两个选择都保留在进程内。 ### 注册主题 @@ -69,6 +69,8 @@ kind: "package-reference" `--dsw-alias-label-shimmer` 为共享文字扫光提供叠加色:浅色配色使用 30% alpha 的黑色,深色配色使用 45% alpha 的白色。`--dsw-alias-label-deep-diving` 和 `--dsw-alias-label-deep-diving-shimmer` 提供蓝色活动文本和扫光颜色;深色主题使用更亮、饱和度更低的文字,并以更亮的蓝色扫光。 +`--dsw-alias-turn-trigger-*` token 分别定义深浅主题下轮次触发通知的常态和悬停底色。 深色提示使用更亮的交互层色,使静止卡片与文本记录背景保持区分。 + `brand-font.css` 导出本地 Montserrat Light、Regular 和 Medium 字体(正体、字重 300、400 和 500),`lib/styles/` 同时提供 `montserrat-light.woff2`、`montserrat-regular.woff2`、`montserrat-medium.woff2` 及其 SIL Open Font License。Desktop 将同一份样式表、字体和许可证打包,用于欢迎页品牌文字的离线显示;普通界面保留系统字体栈。 `corner-shape.css` 平滑所有圆角:在 `@supports (corner-shape: superellipse(1.5))` 内定义 `--dsw-corner-shape`,并通过通配选择器应用到所有元素及其 `::before`/`::after`,因此不支持 `corner-shape` 的引擎保持普通圆弧。正圆形状——`border-radius: 50%` 的圆与胶囊半径——因超级椭圆会使其变形,须在所属组件样式表中把 `corner-shape: round` 与半径声明配对;corner-shape 样式表 spec 跨全部包样式表强制这一配对。 diff --git a/packages/client/ui-theme/src/styles/design-platform.css b/packages/client/ui-theme/src/styles/design-platform.css index af094e9779..834b269295 100644 --- a/packages/client/ui-theme/src/styles/design-platform.css +++ b/packages/client/ui-theme/src/styles/design-platform.css @@ -262,6 +262,8 @@ body { --dsw-alias-toast-label: var(--dsw-static-neutral-bluish-00); --dsw-alias-tooltip-bg: var(--dsw-static-neutral-bluish-850); --dsw-alias-tooltip-key-bg: color-mix(in srgb, var(--dsw-alias-tooltip-bg), white 18%); + --dsw-alias-turn-trigger-bg: var(--dsw-alias-markdown-code-block); + --dsw-alias-turn-trigger-bg-hover: var(--dsw-alias-interactive-bg-hover); --dsw-specific-bubble-highlight: var(--dsw-static-deepseek-200); --dsw-specific-bubble: var(--dsw-static-deepseek-50); --dsw-specific-input-major: var(--dsw-static-neutral-bluish-00); @@ -375,6 +377,8 @@ body[data-ds-dark-theme] { --dsw-alias-switch-thumb: var(--dsw-static-neutral-bluish-400); --dsw-alias-toast-bg: var(--dsw-static-neutral-bluish-750); --dsw-alias-toast-label: var(--dsw-static-neutral-bluish-00); + --dsw-alias-turn-trigger-bg: var(--dsw-alias-interactive-bg-hover); + --dsw-alias-turn-trigger-bg-hover: var(--dsw-alias-interactive-bg-active); --dsw-alias-tooltip-bg: var(--dsw-static-neutral-bluish-750); --dsw-alias-tooltip-key-bg: color-mix(in srgb, var(--dsw-alias-tooltip-bg), white 18%); --dsw-specific-bubble-highlight: var(--dsw-static-neutral-bluish-750); diff --git a/packages/client/ui-theme/src/styles/gradient-shadow-text.css b/packages/client/ui-theme/src/styles/gradient-shadow-text.css index 1bd151f39a..863776c7d3 100644 --- a/packages/client/ui-theme/src/styles/gradient-shadow-text.css +++ b/packages/client/ui-theme/src/styles/gradient-shadow-text.css @@ -48,14 +48,11 @@ body[data-ds-dark-theme] { /* 由 @deepseek-figma-plugin/custom-variable-name 插件导出 */ /* Markdown 阶梯整体按 0.875(正文 16 → 14)等比缩放,四舍五入到整数像素; 重新从 Figma 导出时需要保留这一缩放。 */ -/* 正文字号调节:--dsh-content-font-size 由主题 bootstrap 与 ThemePresenter - 写在 body 上(整数 px,12–17,默认 14)。标题与正文变体按同一像素增量 - (--dsh-content-font-delta)随动,保持层级。低一档文本(think、工具/bash - 行的标题与摘要、markdown 表格、流内次级说明等)统一读 - --dsh-content-font-size-secondary:设置 ≤14 时为设置值 −1,>14 时为 - 设置值 −2(12→11、13→12、14→13、15→13、16→14、17→15)。式中 - max(13px,·) 的作用是让低设置取 −1 而非 −2,不是结果下限——12 设置下 - 层级触底 11px。small、code 属于密集次级文本,保持固定。 */ +/* Bootstrap and ThemePresenter set the integer content size on body (10–22px, + default 14px). Headings and body variants share its pixel delta. Secondary + text uses size − 1 through 14px, then size − 2: 10→9, 14→13, 15→13, 22→20. + max(13px, ...) selects the delta, not a font-size floor. Small and code + typography keep their fixed sizes. */ body { --dsh-content-font-delta: calc(var(--dsh-content-font-size, 14px) - 14px); --dsh-content-font-size-secondary: min(calc(var(--dsh-content-font-size, 14px) - 1px), max(13px, calc(var(--dsh-content-font-size, 14px) - 2px))); diff --git a/packages/client/ui-theme/src/theme-settings.ts b/packages/client/ui-theme/src/theme-settings.ts index 11f8789d6c..d00af8e9cf 100644 --- a/packages/client/ui-theme/src/theme-settings.ts +++ b/packages/client/ui-theme/src/theme-settings.ts @@ -21,10 +21,10 @@ export type ThemePreference = typeof THEME_PREFERENCES[number] export const DEFAULT_PREFERENCE: ThemePreference = 'system' /** Smallest accepted content font size (px). */ -export const FONT_SIZE_MIN = 12 +export const FONT_SIZE_MIN = 10 /** Largest accepted content font size (px). */ -export const FONT_SIZE_MAX = 17 +export const FONT_SIZE_MAX = 22 /** Content font size when the user-settings document has no override (px). */ export const DEFAULT_FONT_SIZE = 14 diff --git a/packages/client/ui-theme/tests/boot-theme.client.spec.ts b/packages/client/ui-theme/tests/boot-theme.client.spec.ts index 6a0141ea87..b6811abe11 100644 --- a/packages/client/ui-theme/tests/boot-theme.client.spec.ts +++ b/packages/client/ui-theme/tests/boot-theme.client.spec.ts @@ -77,8 +77,8 @@ describe('theme bootstrap row', () => { it('writes the durable content font size and defaults it to 14px', () => { mockSystemDark(false) - executeBootstrap('light', 17) - expect(document.body.style.getPropertyValue('--dsh-content-font-size')).toBe('17px') + executeBootstrap('light', 22) + expect(document.body.style.getPropertyValue('--dsh-content-font-size')).toBe('22px') executeBootstrap('light') expect(document.body.style.getPropertyValue('--dsh-content-font-size')).toBe('14px') }) diff --git a/packages/client/ui-theme/tests/config.host.spec.ts b/packages/client/ui-theme/tests/config.host.spec.ts index a3309b9887..ebe1f3ca99 100644 --- a/packages/client/ui-theme/tests/config.host.spec.ts +++ b/packages/client/ui-theme/tests/config.host.spec.ts @@ -28,11 +28,13 @@ describe('ui-theme host', () => { const configuration = await liveConfig(ctx, { Config, apply }) const { fiber } = configuration expect(plainConfig(configuration.fiber.config)).toEqual({ preference: DEFAULT_PREFERENCE, fontSize: 14 }) - await configuration.update({ preference: 'dark', fontSize: 16 }) - expect(plainConfig(configuration.fiber.config)).toEqual({ preference: 'dark', fontSize: 16 }) + await configuration.update({ preference: 'dark', fontSize: 10 }) + expect(plainConfig(configuration.fiber.config)).toEqual({ preference: 'dark', fontSize: 10 }) + await configuration.update({ fontSize: 22 }) + expect(plainConfig(configuration.fiber.config)).toEqual({ preference: 'dark', fontSize: 22 }) await expect(configuration.update({ preference: 'sepia' })).rejects.toThrow() - await expect(configuration.update({ fontSize: 11 })).rejects.toThrow() - await expect(configuration.update({ fontSize: 18 })).rejects.toThrow() + await expect(configuration.update({ fontSize: 9 })).rejects.toThrow() + await expect(configuration.update({ fontSize: 23 })).rejects.toThrow() await fiber.dispose() }) @@ -51,10 +53,10 @@ describe('ui-theme host', () => { expect(rowText(rows[0])).toContain('@media(prefers-color-scheme:dark)') expect(rowText(rows[1])).toContain('const preference = "system"') expect(rowText(rows[1])).toContain('"14px"') - await configuration.update({ preference: 'dark', fontSize: 17 }) + await configuration.update({ preference: 'dark', fontSize: 22 }) expect(rowText(collect(ctx)[0])).toContain('color-scheme:dark') expect(rowText(collect(ctx)[1])).toContain('const preference = "dark"') - expect(rowText(collect(ctx)[1])).toContain('"17px"') + expect(rowText(collect(ctx)[1])).toContain('"22px"') await fiber.dispose() expect(collect(ctx)).toEqual([{ kind: 'script', placement: 'head', text: 'window.afterTheme=true' }]) }) diff --git a/packages/client/ui-theme/tests/expected/radius-exceptions.expected.json b/packages/client/ui-theme/tests/expected/radius-exceptions.expected.json index fa33cce684..2f962a9444 100644 --- a/packages/client/ui-theme/tests/expected/radius-exceptions.expected.json +++ b/packages/client/ui-theme/tests/expected/radius-exceptions.expected.json @@ -1,4 +1,10 @@ { + "ui-deliverables/src/client/ChangedFiles.module.css": [ + ".tile: border-radius: 10px" + ], + "ui-deliverables/src/client/Deliverables.module.css": [ + ".fileIcon: border-radius: 10px" + ], "ui-schedule/src/client/ClockPicker.module.css": [ ".option: border-radius: 10px" ], diff --git a/packages/client/ui-theme/tests/font-size-row.client.spec.tsx b/packages/client/ui-theme/tests/font-size-row.client.spec.tsx index f75138b44a..1a77e822c2 100644 --- a/packages/client/ui-theme/tests/font-size-row.client.spec.tsx +++ b/packages/client/ui-theme/tests/font-size-row.client.spec.tsx @@ -87,11 +87,19 @@ describe('FontSizeRow', () => { }) it('disables the outward arrow at each bound', () => { - mount(17) + const b = mount(21) + fireEvent.click(arrow('Increase font size')) + expect(b.setFontSize).toHaveBeenCalledWith(22) + act(() => { b.store.actions.sync(22, 1) }) + expect(screen.getByText('22')).toBeDefined() expect(arrow('Increase font size').disabled).toBe(true) expect(arrow('Decrease font size').disabled).toBe(false) cleanup() - mount(12) + const c = mount(11) + fireEvent.click(arrow('Decrease font size')) + expect(c.setFontSize).toHaveBeenCalledWith(10) + act(() => { c.store.actions.sync(10, 1) }) + expect(screen.getByText('10')).toBeDefined() expect(arrow('Increase font size').disabled).toBe(false) expect(arrow('Decrease font size').disabled).toBe(true) }) diff --git a/packages/client/ui-theme/tests/theme.client.spec.ts b/packages/client/ui-theme/tests/theme.client.spec.ts index a50ed3e7e2..2f0033310f 100644 --- a/packages/client/ui-theme/tests/theme.client.spec.ts +++ b/packages/client/ui-theme/tests/theme.client.spec.ts @@ -36,31 +36,31 @@ describe('ThemeRuntime', () => { it('seeds the initial font size from the boot-script body variable, ignoring junk', () => { // The Host boot script writes the durable size on body before any plugin // runs; the first snapshot must match it so activation never flashes 14. - document.body.style.setProperty('--dsh-content-font-size', '16px') + document.body.style.setProperty('--dsh-content-font-size', '22px') try { - expect(make().theme.getTheme().fontSize).toBe(16) - document.body.style.setProperty('--dsh-content-font-size', '99px') + expect(make().theme.getTheme().fontSize).toBe(22) + document.body.style.setProperty('--dsh-content-font-size', '23px') expect(make().theme.getTheme().fontSize).toBe(14) } finally { document.body.style.removeProperty('--dsh-content-font-size') } }) - it('setFontSize switches, writes through the scope, and republishes; same value is a no-op', () => { + it.each([10, 22])('setFontSize(%i) switches, writes through the scope, and republishes; same value is a no-op', (fontSize) => { const { theme, events, host } = make() - theme.setFontSize(17) - expect(theme.getTheme().fontSize).toBe(17) - expect(host.set).toHaveBeenCalledWith('fontSize', 17) + theme.setFontSize(fontSize) + expect(theme.getTheme().fontSize).toBe(fontSize) + expect(host.set).toHaveBeenCalledWith('fontSize', fontSize) expect(events).toHaveLength(1) - theme.setFontSize(17) + theme.setFontSize(fontSize) expect(events).toHaveLength(1) expect(host.set).toHaveBeenCalledOnce() }) it('rejects out-of-range and fractional font sizes', () => { const { theme, events, host } = make() - for (const px of [11, 18, 14.5, Number.NaN]) { - expect(() => { theme.setFontSize(px) }).toThrow('outside 12..17') + for (const px of [9, 23, 14.5, Number.NaN]) { + expect(() => { theme.setFontSize(px) }).toThrow('outside 10..22') } expect(events).toHaveLength(0) expect(host.set).not.toHaveBeenCalled() diff --git a/packages/client/ui-tool/README.i18n.yaml b/packages/client/ui-tool/README.i18n.yaml index fc442d774a..3f6c0e0f3c 100644 --- a/packages/client/ui-tool/README.i18n.yaml +++ b/packages/client/ui-tool/README.i18n.yaml @@ -30,8 +30,8 @@ en: 2959b10b01e08a7b zh: 7084ca22650cbb5b /deepseek-ai-dsh-client-ui-tool/understand-the-implementation/cards: - en: 401a0118de2dba33 - zh: 9efe590dd485c336 + en: 15c9263d8078527f + zh: 011ff8645856dd8e /deepseek-ai-dsh-client-ui-tool/further-exploration: en: 886059e6bd594ebf zh: 94cfe8d0a789f413 diff --git a/packages/client/ui-tool/README.md b/packages/client/ui-tool/README.md index 86e5287626..a48788477b 100644 --- a/packages/client/ui-tool/README.md +++ b/packages/client/ui-tool/README.md @@ -75,7 +75,7 @@ The slot-injected `useToolCallArgumentsPartial` Hook lazily subscribes to the ow Every card is read in place in the call tree; there is no second, full-height presentation of a selected call. Row renderers share one pure card model for each terminal, read, diff, search, and web card, and the image card's gallery renders through the tool-owned `tool.call.images` slot. These models validate raw call arguments, result content, failure state, persisted metadata, PTC dispatch `parentCallId`, and Session path facts. Unsupported or malformed inputs use flattened Tool result text. A file-path summary opens the file through the owner's `openFile`, which the chat view routes to the right Sidebar's text preview; `inspect` opens the trajectory view and is absent when that View is unavailable; cards then omit Inspect. Card-specific limits and fallback rules for the terminal, diff, read, search, and web cards remain in [the ui-primitives README](../ui-primitives/README.md); the image card's model in this package carries its own fallback rules. -Chat diff cards keep nine rows before folding, enough for a file header, one removed/added pair, and three context lines on either side. The tool row shows the primitive's exact or coarse-replacement counts; the expanded card contains the diff body without a totals footer. +Chat diff cards keep nine rows before folding, enough for a file header, one removed/added pair, and three context lines on either side. The tool row shows the primitive's exact or coarse-replacement counts; the expanded card contains the diff body without a totals footer. Hovering the tool header colors its added count green and removed count red; both retain those colors while the card is expanded. An Auto denial takes precedence over keyed specialized views. Its generic row preserves the call identity, omits raw arguments, and normalizes the stored reason only for display: trim surrounding whitespace and collapse line separators to spaces, with localized fallback for an empty result. Session and SDK error details keep the original reason. diff --git a/packages/client/ui-tool/README.zh.md b/packages/client/ui-tool/README.zh.md index f33ec97dc2..baa584fbe9 100644 --- a/packages/client/ui-tool/README.zh.md +++ b/packages/client/ui-tool/README.zh.md @@ -75,7 +75,7 @@ slot 注入的 `useToolCallArgumentsPartial` 钩子按需订阅所属 Step 的 ` 每张卡片都直接在调用树中查看;选中调用后不会再显示第二个全高视图。行 renderer 为 terminal、read、diff、search 和 web 卡片各复用同一个纯 card model,image 卡片的图库经由工具自有 `tool.call.images` slot 渲染。这些 model 校验原始调用参数、结果内容、失败状态、持久 metadata、PTC dispatch 的 `parentCallId` 与会话路径信息。不受支持或格式错误的输入使用压平的工具结果文本。文件路径摘要经属主的 `openFile` 打开文件,chat 视图把它路由到右侧 Sidebar 的文本预览;`inspect` 打开轨迹视图;该视图不可用时不提供此回调,卡片随之隐藏 Inspect。terminal、diff、read、search 与 web 卡片的上限与 fallback 规则仍由 [ui-primitives README](../ui-primitives/README.zh.md) 负责;image 卡片的 fallback 规则由本包内的 card model 自行承载。 -Chat diff 卡片在折叠前保留九行,足以容纳文件标题、一对删除与新增行及两侧各三行上下文。工具行显示原语提供的精确或粗粒度替换统计;展开卡片包含差异正文,不显示底部统计。 +Chat diff 卡片在折叠前保留九行,足以容纳文件标题、一对删除与新增行及两侧各三行上下文。工具行显示原语提供的精确或粗粒度替换统计;展开卡片包含差异正文,不显示底部统计。悬停工具标题行时,新增数量显示绿色、删除数量显示红色;卡片展开期间保留这两种颜色。 Auto 拒绝优先于按工具名选择的专门视图。其通用行保留调用身份、省略原始参数,并且只在显示时归一化存储的理由:去除首尾空白,把行分隔符折叠为空格,结果为空时使用本地化通用理由。Session 与 SDK 错误详情保留原始理由。 diff --git a/packages/client/ui-tool/src/client/tool/components/ToolRow.module.css b/packages/client/ui-tool/src/client/tool/components/ToolRow.module.css index 0c57d41675..5be4d641be 100644 --- a/packages/client/ui-tool/src/client/tool/components/ToolRow.module.css +++ b/packages/client/ui-tool/src/client/tool/components/ToolRow.module.css @@ -15,28 +15,12 @@ flex-shrink: 0; } -/* Cordis lifecycle tools retain their generic row mechanics while carrying a - shared product accent and tool-owned action title. */ -.root[data-tool^='cordis_'] .leading, -.root[data-tool^='cordis_'] .title { - color: var(--dsw-alias-state-business-primary); -} - .root[data-tool^='cordis_'] .title { font-weight: 500; } -.root[data-tool^='cordis_'] .sep { - background: var(--dsw-alias-state-business-primary); -} - -.chevron { - color: var(--dsw-alias-label-secondary); -} - .title { font-weight: 400; - transition: color 100ms ease; } .sep { @@ -56,8 +40,6 @@ white-space: nowrap; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); - color: var(--dsw-alias-label-tertiary); - transition: color 100ms ease; } /* Trailing summary fragment kept out of .summary's ellipsis, for a count whose @@ -71,31 +53,29 @@ white-space: nowrap; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); - color: var(--dsw-alias-label-tertiary); - transition: color 100ms ease; -} - -.row:hover .title, -.row:hover .summary:not(.errorSummary):not(.stoppedSummary), -.row:hover .summarySuffix { - color: var(--dsw-alias-label-primary); } /* Diff-row +/- totals: the code font, matching the diff body the numbers summarize; a wider gap keeps the digits from reading as part of the path. Two px under the secondary tier (still riding the axis): mono digits read - optically larger than the sans path at the same size. Caption, one step - dimmer than the suffix's tertiary, so the digits stay behind the path. The + optically larger than the sans path at the same size. The half-pixel nudge closes the baseline gap the size difference leaves under the row's box-centering (a transform, so flex layout is untouched). */ .diffStat { margin-left: 10px; font-family: var(--ds-font-family-code); font-size: calc(var(--dsh-content-font-size-secondary, 13px) - 2px); - color: var(--dsw-alias-label-caption); transform: translateY(0.5px); } +.row:is(:hover, [aria-expanded='true']) .diffAdded { + color: var(--dsw-alias-state-success-primary); +} + +.row:is(:hover, [aria-expanded='true']) .diffRemoved { + color: var(--dsw-alias-state-error-primary); +} + /* File-tool path: same type as .summary, with a persistent link affordance. Dotted dimmed underline: visible enough to say "clickable", light enough that a long path doesn't read as one heavy rule under the row. Shrink-to-fit @@ -115,19 +95,12 @@ text-align: left; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); - color: var(--dsw-alias-label-secondary); + color: inherit; text-decoration: underline dotted; - text-decoration-color: var(--dsw-alias-label-tertiary); + text-decoration-color: currentColor; text-decoration-thickness: 1px; text-underline-offset: 3px; cursor: pointer; - transition: color 100ms ease; -} - -.row:hover .fileLink, -.fileLink:hover { - color: var(--dsw-alias-label-primary); - text-decoration-color: currentColor; } /* Error row's collapsed summary: the failure's first line in the error color. */ diff --git a/packages/client/ui-tool/src/client/tool/components/ToolRow.tsx b/packages/client/ui-tool/src/client/tool/components/ToolRow.tsx index f4a1e0b486..d91a72d87f 100644 --- a/packages/client/ui-tool/src/client/tool/components/ToolRow.tsx +++ b/packages/client/ui-tool/src/client/tool/components/ToolRow.tsx @@ -55,6 +55,7 @@ export interface ToolRowProps { errorSummary?: string | null | undefined /** Terminal card; card fields are mutually exclusive and replace text sections. */ terminal?: TerminalCardModel | null | undefined + /** Diff card with inline totals colored on header hover and while expanded. */ diff?: DiffCardModel | null | undefined read?: ReadCardModel | null | undefined /** @@ -183,11 +184,7 @@ export const ToolRow = memo(function ToolRow({ const summaryText = failureLine ?? normalSummary // The tool row keeps the diff's +/- totals visible while its body is collapsed. // An explicit summarySuffix overrides the diff totals. - const diffStat = useMemo(() => { - if (diffBody === null) return null - const { added, removed } = diffTotals(diffBody.card.diffs) - return `+${added} -${removed}` - }, [diffBody]) + const diffStat = useMemo(() => diffBody === null ? null : diffTotals(diffBody.card.diffs), [diffBody]) const settledWithCue = state === 'error' || state === 'stopped' const suffix = settledWithCue ? null : summarySuffix ?? diffStat const openFile = useMemo(() => filePath !== undefined && onOpenFile !== undefined && !settledWithCue @@ -246,10 +243,12 @@ export const ToolRow = memo(function ToolRow({ )} {suffix !== null && ( - {suffix} + + {typeof suffix === 'string' ? suffix : <>{`+${suffix.added}`}{' '}{`-${suffix.removed}`}} + )} - ), [diffStat, summaryLinkKeyDown, linkHref, openFile, running, state, suffix, summaryText]) + ), [summaryLinkKeyDown, linkHref, openFile, state, suffix, summaryText]) const expandedContent = useMemo(() => open ? (
{askQuestionBody !== null @@ -361,7 +360,6 @@ export const ToolRow = memo(function ToolRow({ rowClassName={css.row} leadingClassName={css.leading} titleClassName={css.title} - chevronClassName={css.chevron} icon={icon} title={title} running={running} diff --git a/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css b/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css index c58a5df600..6d44e524fb 100644 --- a/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css +++ b/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css @@ -89,6 +89,18 @@ align-items: center; height: calc(24px + var(--dsh-content-font-delta, 0px)); min-width: 0; + color: var(--dsw-alias-label-tertiary); + transition: color 100ms ease; +} + +.root:hover { + color: var(--dsw-alias-label-secondary); +} + +@media (prefers-reduced-motion: reduce) { + .root { + transition: none; + } } .leading { @@ -100,7 +112,7 @@ align-items: center; justify-content: center; margin-right: 6px; - color: var(--dsw-alias-label-tertiary); + color: inherit; } /* Same glyph-scaling rule as DisclosureRow's leading: the CSS edge overrides @@ -115,7 +127,7 @@ } .chevron { - color: var(--dsw-alias-label-secondary); + color: inherit; } /* Hover preview on the expandable row: the idle icon crossfades (100ms) into @@ -146,8 +158,6 @@ flex: none; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); - color: var(--dsw-alias-label-secondary); - transition: color 100ms ease; } .sep { @@ -167,13 +177,6 @@ white-space: nowrap; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); - color: var(--dsw-alias-label-tertiary); - transition: color 100ms ease; -} - -.root:hover .title, -.root:hover .summary:not(.errorSummary):not(.stoppedSummary) { - color: var(--dsw-alias-label-primary); } /* Error row's collapsed summary: the failure's first line in the error color. */ diff --git a/packages/client/ui-tool/tests/diff-card.client.spec.tsx b/packages/client/ui-tool/tests/diff-card.client.spec.tsx index a80446e058..0274f74751 100644 --- a/packages/client/ui-tool/tests/diff-card.client.spec.tsx +++ b/packages/client/ui-tool/tests/diff-card.client.spec.tsx @@ -246,9 +246,10 @@ describe('FileMutationRow diff card', () => { meta: { diffs: [] }, }), 'write')} />) // The collapsed row already carries the card's +/- totals beside the path. - expect(view.getByText('+1 -0')).toBeTruthy() + expect(view.container.querySelector('[data-disclosure-row]')?.textContent).toContain('+1 -0') toggleRow(view) - expect(view.getAllByText('+1 -0')).toHaveLength(1) + expect(view.getAllByText('+1')).toHaveLength(1) + expect(view.getAllByText('-0')).toHaveLength(1) expect(view.getByText('hello fixture')).toBeTruthy() }) diff --git a/packages/client/ui-tool/tests/tool-row-styles.client.spec.ts b/packages/client/ui-tool/tests/tool-row-styles.client.spec.ts index db05c7f769..399c588713 100644 --- a/packages/client/ui-tool/tests/tool-row-styles.client.spec.ts +++ b/packages/client/ui-tool/tests/tool-row-styles.client.spec.ts @@ -25,9 +25,15 @@ describe('ToolRow.module.css summary line', () => { it.each([ ['ToolRow', '../src/client/tool/components/ToolRow.module.css'], ['BashRow', '../src/client/tool/toolviews/bash-sample.module.css'], - ] as const)('%s keeps stopped and error summaries outside hover darkening', (_name, path) => { + ] as const)('%s leaves row text color to the shared disclosure header', (_name, path) => { + // The header supplies tertiary at rest and secondary on hover; a tool sheet + // that restates a row text color, or brightens the row text on hover, would + // pin one state against the shared process-row colors. The Inspect button + // keeps its own solid hover fill and is not row text. const sheet = readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8') - expect(sheet).toContain('.summary:not(.errorSummary):not(.stoppedSummary)') + .replace(/\/\*[\s\S]*?\*\//g, ' ') + expect(sheet).not.toMatch(/\.(title|summary|summarySuffix)\s*\{[^{}]*\bcolor:/) + expect(sheet).not.toMatch(/:hover\s+\.(title|summary|summarySuffix)\b/) }) it('keeps the summary suffix on one line and unshrunk', () => { diff --git a/packages/client/ui-tool/tests/tool-row.client.spec.tsx b/packages/client/ui-tool/tests/tool-row.client.spec.tsx index f9338cac00..954083d8a6 100644 --- a/packages/client/ui-tool/tests/tool-row.client.spec.tsx +++ b/packages/client/ui-tool/tests/tool-row.client.spec.tsx @@ -309,10 +309,11 @@ describe('ToolRow', () => { newText: 'start\nsecond\nthird\nnew\nfourth\nfifth\nend', }] }, }} />) - expect(view.getByText('+1 -1')).toBeTruthy() + expect(view.container.querySelector('[data-disclosure-row]')?.textContent).toContain('+1 -1') expect(view.container.querySelector('[data-diff]')).toBeNull() fireEvent.click(view.getByRole('button')) - expect(view.getAllByText('+1 -1')).toHaveLength(1) + expect(view.getAllByText('+1')).toHaveLength(1) + expect(view.getAllByText('-1')).toHaveLength(1) expect(view.getAllByText('start')).toHaveLength(1) expect(view.getAllByText('end')).toHaveLength(1) expect(view.getByText('old', { exact: true })).toBeTruthy() diff --git a/packages/client/ui-workflow-run/README.i18n.yaml b/packages/client/ui-workflow-run/README.i18n.yaml index d34e6a8bd6..90aefbb9a3 100644 --- a/packages/client/ui-workflow-run/README.i18n.yaml +++ b/packages/client/ui-workflow-run/README.i18n.yaml @@ -18,8 +18,8 @@ en: 9712b1ed5f00f24f zh: c8f72b2ef448d9af /deepseek-ai-dsh-client-ui-workflow-run/use-this-package/navigating-the-node: - en: ced9d82bdf540585 - zh: 34651e03ed937508 + en: 99b67c86b6cf2031 + zh: b3c5e998600525cb /deepseek-ai-dsh-client-ui-workflow-run/use-this-package/state-and-completion: en: 03784e501ae8f70a zh: 90b32daccf577780 diff --git a/packages/client/ui-workflow-run/README.md b/packages/client/ui-workflow-run/README.md index f48efe1f15..9eb9ed8750 100644 --- a/packages/client/ui-workflow-run/README.md +++ b/packages/client/ui-workflow-run/README.md @@ -29,7 +29,7 @@ A top-level workflow run through `dsh-tool-workflow` appears in the conversation ### Navigating the node -The run uses a 32-pixel row with persistent chevrons, an inline state dot, and status text; phases use disclosure rows with title and member count in the main area and a fixed aggregate-status tail; members use a 16-pixel dot slot, a truncating name area, and a fixed status column. Opening a member's child Session requires both member and child to be running, and the child must belong to the current Session's direct-child catalog. Child activity uses the unified UI status, falling back to the Session summary when unknown. Session summaries do not establish membership; absent, wrong-parent, or terminal catalog entries remain non-interactive. +Run and phase headers keep 6px gaps between their title, count, and status fields, with the status aligned to the right. The run uses a 32-pixel row with persistent chevrons, an inline state dot, and status text; phases use disclosure rows with title and member count in the main area and a fixed aggregate-status tail; members use a 16-pixel dot slot, a truncating name area, and a fixed status column. Opening a member's child Session requires both member and child to be running, and the child must belong to the current Session's direct-child catalog. Child activity uses the unified UI status, falling back to the Session summary when unknown. Session summaries do not establish membership; absent, wrong-parent, or terminal catalog entries remain non-interactive. ### State and completion diff --git a/packages/client/ui-workflow-run/README.zh.md b/packages/client/ui-workflow-run/README.zh.md index 9fa6f59b68..8c2989b746 100644 --- a/packages/client/ui-workflow-run/README.zh.md +++ b/packages/client/ui-workflow-run/README.zh.md @@ -29,7 +29,7 @@ kind: "package-reference" ### 导航节点 -运行使用 32 像素行,带常驻 chevron、行内状态点与状态文字;阶段使用 disclosure 行,在主区显示标题与成员数、在固定尾部显示聚合状态;成员使用 16 像素状态点槽、可省略名称区与固定状态列。打开成员的子 Session 需要成员与子级均在运行,且子级属于当前 Session 的直接子级目录。子级活动状态使用统一 UI status,未知时使用 Session 摘要。Session 摘要不建立成员关系;目录中缺失、父级不符或终态的条目不可交互。 +运行与阶段标题行在标题、数量和状态字段之间保留 6px 间距,状态靠右对齐。运行使用 32 像素行,带常驻 chevron、行内状态点与状态文字;阶段使用 disclosure 行,在主区显示标题与成员数、在固定尾部显示聚合状态;成员使用 16 像素状态点槽、可省略名称区与固定状态列。打开成员的子 Session 需要成员与子级均在运行,且子级属于当前 Session 的直接子级目录。子级活动状态使用统一 UI status,未知时使用 Session 摘要。Session 摘要不建立成员关系;目录中缺失、父级不符或终态的条目不可交互。 ### 状态与完成 diff --git a/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css b/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css index 15eab9b06f..ae754eb363 100644 --- a/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css +++ b/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css @@ -29,14 +29,14 @@ align-items: center; justify-content: center; margin-right: 0; - color: var(--dsw-alias-label-tertiary); + color: inherit; } .runTitle { overflow: hidden; flex: none; max-width: 42%; - color: var(--dsw-alias-label-secondary); + color: inherit; font-size: var(--dsh-content-font-size-secondary, 13px); font-weight: 510; line-height: calc(24px + var(--dsh-content-font-delta, 0px)); @@ -48,7 +48,7 @@ overflow: hidden; flex: 1; min-width: 0; - color: var(--dsw-alias-label-tertiary); + color: inherit; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px)); text-overflow: ellipsis; @@ -65,7 +65,7 @@ font-size: 11px; font-weight: 510; line-height: 16px; - color: var(--dsw-alias-label-secondary); + color: inherit; white-space: nowrap; } @@ -93,7 +93,7 @@ align-items: center; justify-content: center; margin-right: 0; - color: var(--dsw-alias-label-tertiary); + color: inherit; } .phaseTitle { @@ -101,7 +101,7 @@ flex: 0 1 auto; min-width: 0; max-width: 42%; - color: var(--dsw-alias-label-secondary); + color: inherit; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(24px + var(--dsh-content-font-delta, 0px)); text-overflow: ellipsis; @@ -112,7 +112,7 @@ overflow: hidden; flex: 1; min-width: 0; - color: var(--dsw-alias-label-tertiary); + color: inherit; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px)); text-overflow: ellipsis; @@ -125,7 +125,7 @@ /* Column width scales with the text (~132/13 ≈ 10px per font-size px) so larger sizes keep the same status text visible instead of truncating. */ width: calc(132px + var(--dsh-content-font-delta-secondary, 0px) * 10); - color: var(--dsw-alias-label-secondary); + color: inherit; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px)); text-align: right; diff --git a/packages/client/ui-workspace/README.i18n.yaml b/packages/client/ui-workspace/README.i18n.yaml index bf8d40aa1d..1d71954157 100644 --- a/packages/client/ui-workspace/README.i18n.yaml +++ b/packages/client/ui-workspace/README.i18n.yaml @@ -27,8 +27,8 @@ en: 9eab3bfbaf98c987 zh: 54e7e141dae5c9e8 /deepseek-ai-dsh-client-ui-workspace/use-this-package/managing-sessions: - en: f6c3a6cfd74f89c0 - zh: dbf60c29500e9ed1 + en: 6b5c96c5b723c5f8 + zh: f8ae5f73860dce45 /deepseek-ai-dsh-client-ui-workspace/use-this-package/pending-interactions: en: 9fb88260380bf093 zh: 3bace2b96ecd4a82 diff --git a/packages/client/ui-workspace/README.md b/packages/client/ui-workspace/README.md index 7ac6134f4e..7a5f94d110 100644 --- a/packages/client/ui-workspace/README.md +++ b/packages/client/ui-workspace/README.md @@ -57,7 +57,7 @@ Archive commits without a confirmation dialog for a quiet Session and retains th Session update times use the tertiary label color, including on archived rows. A Session title wider than its row is clipped with an ellipsis at rest. Hovering the row scrolls the title to its far edge — the incremented title of a fork, for example — and reveals it without the ellipsis; leaving the row returns the title to its start. -The keyboard reference exposes New Session, Search sessions, Add workspace, Rename session, Fork session, and Archive session. Desktop defaults use the platform's primary modifier, with Mod+K for search; Windows and macOS Web use the [shortcut service’s platform defaults](../shortcuts/README.md); Linux Web leaves these commands unbound until configured. Button tooltips and Session row menus display the effective bindings. Clicking a menu item targets that row; pressing its shortcut targets the main Session. Windows and macOS Desktop bindings also execute from terminal input and modal dialogs; other environments follow the command's region and modal restrictions. Search and rename requests belong to this package; input drafts remain in the browser. The directory picker rejects another open while selection or Workspace adoption is pending. Fork captures the source Session and uses the row action's Host operation to select its last completed turn, without loading older Client history. Missing or blank Sessions are unavailable; the Host rejects a source with no completed turn. A rejected shortcut fork preserves the current selection, displays a localized notification, and can be retried; unexpected failures also retain diagnostic logging. +The keyboard reference exposes New Session, Search sessions, Add workspace, Rename session, Fork session, and Archive session. Desktop defaults use the platform's primary modifier, with Mod+K for search; Windows and macOS Web use the [shortcut service’s platform defaults](../shortcuts/README.md); Linux Web leaves these commands unbound until configured. Button tooltips and Session row menus display the effective bindings. Clicking a menu item targets that row; pressing its shortcut targets the main Session. Desktop and Windows/macOS Web rename use Mod+Alt+G, leaving Mod+Shift+R to browser reload. Rename requires a nonblank main Conversation with no modal obscuring it; a retained Session behind another main panel is unavailable. Other Windows and macOS Desktop bindings also execute from terminal input and modal dialogs; other environments follow the command's region and modal restrictions. Search and rename requests belong to this package; input drafts remain in the browser. The directory picker rejects another open while selection or Workspace adoption is pending. Fork captures the source Session and uses the row action's Host operation to select its last completed turn, without loading older Client history. Missing or blank Sessions are unavailable; the Host rejects a source with no completed turn. A rejected shortcut fork preserves the current selection, displays a localized notification, and can be retried; unexpected failures also retain diagnostic logging. ### Pending interactions diff --git a/packages/client/ui-workspace/README.zh.md b/packages/client/ui-workspace/README.zh.md index a9bd250a6d..634d293535 100644 --- a/packages/client/ui-workspace/README.zh.md +++ b/packages/client/ui-workspace/README.zh.md @@ -57,7 +57,7 @@ Session 行内的 Rename 操作打开一个以该行显示标题预填的对话 会话更新时间使用 tertiary 文本色,包括已归档行。标题宽于所在行时,静止状态以省略号裁切。把指针停在行上,标题会滚动到远端——例如 fork 递增后的标题——并在揭示时不显示省略号;指针离开后标题回到开头。 -快捷键速查提供新建会话、搜索会话、添加工作区、重命名会话、分叉会话和归档会话。桌面默认使用平台的主修饰键,搜索为 Mod+K;Windows 和 macOS Web 使用[快捷键服务的平台默认值](../shortcuts/README.zh.md);Linux Web 在用户配置前不绑定这些命令。按钮提示和会话行菜单显示当前有效绑定。点击菜单项操作该行,按快捷键操作主会话。Windows 和 macOS Desktop 的绑定也可从终端输入区域和模态对话框中执行;其他环境遵循命令的区域和模态限制。搜索和重命名的打开请求归本包管理,输入草稿保留在浏览器中。目录选择或工作区接纳尚未结束时,目录选择器拒绝重复打开。分叉捕获源会话,并使用会话行相同的 Host 操作选择最近已完成轮次,不读取更早的 Client 历史。没有会话或会话为空时不可用;没有已完成轮次的源会话由 Host 拒绝。快捷键分叉被拒绝时保留当前选择、显示本地化提示,并允许重试;非预期失败还会保留诊断日志。 +快捷键速查提供新建会话、搜索会话、添加工作区、重命名会话、分叉会话和归档会话。桌面默认使用平台的主修饰键,搜索为 Mod+K;Windows 和 macOS Web 使用[快捷键服务的平台默认值](../shortcuts/README.zh.md);Linux Web 在用户配置前不绑定这些命令。按钮提示和会话行菜单显示当前有效绑定。点击菜单项操作该行,按快捷键操作主会话。Desktop 和 Windows/macOS Web 的重命名均使用 Mod+Alt+G,将 Mod+Shift+R 留给浏览器刷新。重命名要求主区域正在显示非空会话,且没有模态对话框遮挡;切换到其他主面板后,后台保留的会话不可作为重命名目标。其他 Windows 和 macOS Desktop 绑定也可从终端输入区域和模态对话框中执行;其他环境遵循命令的区域和模态限制。搜索和重命名的打开请求归本包管理,输入草稿保留在浏览器中。目录选择或工作区接纳尚未结束时,目录选择器拒绝重复打开。分叉捕获源会话,并使用会话行相同的 Host 操作选择最近已完成轮次,不读取更早的 Client 历史。没有会话或会话为空时不可用;没有已完成轮次的源会话由 Host 拒绝。快捷键分叉被拒绝时保留当前选择、显示本地化提示,并允许重试;非预期失败还会保留诊断日志。 ### 待处理交互 diff --git a/packages/client/ui-workspace/src/client/shortcuts.ts b/packages/client/ui-workspace/src/client/shortcuts.ts index 669b240b32..0b308ffd2d 100644 --- a/packages/client/ui-workspace/src/client/shortcuts.ts +++ b/packages/client/ui-workspace/src/client/shortcuts.ts @@ -57,6 +57,7 @@ export function createWorkspaceShortcutControls(): WorkspaceShortcutControls { /** * Register navigation commands against the existing workspace owner. + * Rename requires a nonblank main Conversation with no modal obscuring it. * @param ctx - plugin context with the shortcut, locale, and model services. * @param navigation - session creation and forking from the pointer controls' navigation service. * @param controls - browser-owned opening requests. @@ -93,9 +94,10 @@ export function installWorkspaceShortcuts( const reason = addReason() return reason === null ? { status: 'handled', run: controls.add } : { status: 'blocked', reason } }) - register('session.rename', () => t('rename.session.title'), ['rename session'], 'KeyR', ['primary', 'alt'], ['primary', 'shift'], () => { + register('session.rename', () => t('rename.session.title'), ['rename session'], 'KeyG', ['primary', 'alt'], ['primary', 'alt'], (context) => { const target = current() - return target === undefined ? { status: 'blocked', reason: t('shortcut.noSession') } + return target === undefined || target.blank || context.modal !== null || ctx.layout.panelInfo.getSnapshot().activePanelId !== null + ? { status: 'blocked', reason: t('shortcut.noSession') } : { status: 'handled', run: () => { controls.rename(target.id, target.title?.trim() ?? '') } } }) register('session.fork', () => t('menu.fork'), ['fork session'], 'KeyF', ['primary', 'alt'], ['primary', 'shift'], () => { diff --git a/packages/client/ui-workspace/tests/shortcuts.client.spec.ts b/packages/client/ui-workspace/tests/shortcuts.client.spec.ts index 5d48fc495d..6a11c44b56 100644 --- a/packages/client/ui-workspace/tests/shortcuts.client.spec.ts +++ b/packages/client/ui-workspace/tests/shortcuts.client.spec.ts @@ -7,7 +7,8 @@ import { RemoteError } from '@deepseek-ai/dsh-client-test-runtime' import type { SessionListState, SessionSummary, SessionSnapshot } from '@deepseek-ai/dsh-api-session-controller/client' import { SessionForkError } from '@deepseek-ai/dsh-api-session-controller/client' import type { SessionId } from '@deepseek-ai/dsh-session/types' -import type { ShortcutCommand, ShortcutGesture } from '@deepseek-ai/dsh-client-shortcuts/client' +import type { ShortcutCommand, ShortcutGesture, ShortcutPlatform } from '@deepseek-ai/dsh-client-shortcuts/client' +import type { PanelInfo, MainPanelId } from '@deepseek-ai/dsh-client-ui-layout/client' import { ShortcutRegistry } from '../../shortcuts/src/client/registry.ts' import { createWorkspaceShortcutControls, installWorkspaceShortcuts } from '../src/client/shortcuts.ts' import { en, zh } from '../src/client/locales.ts' @@ -22,10 +23,10 @@ const row = (id: string, main = false): SessionSummary => ({ id: sid(id), title: id, displayTitle: id, cwd: `/workspace/${id}`, blank: false, running: false, updatedAt: 0, retainedBy: main ? { mainView: 1 } : {}, }) -async function bench(runtime: 'web' | 'desktop' = 'desktop') { +async function bench(runtime: 'web' | 'desktop' = 'desktop', platform: ShortcutPlatform = 'macos') { const ctx = new Context() onTestFinished(async () => { await ctx.fiber.dispose() }) - const registry = new ShortcutRegistry(runtime, 'macos') + const registry = new ShortcutRegistry(runtime, platform) const commands = new Map() ctx.provide('shortcuts', { register: (command: ShortcutCommand) => { commands.set(command.id, command) @@ -41,6 +42,8 @@ async function bench(runtime: 'web' | 'desktop' = 'desktop') { sessionId: sid(id), session: { ...history, loadOlder }, }])) ctx.provide('sessions', { list, binding: (id: SessionId) => bindings.get(id) }) + const panelInfo = createSnapshotStore({ activePanelId: null }) + ctx.provide('layout', { panelInfo }) const directory = createSnapshotStore(true) ctx.provide('slots', { entries: () => directory.getSnapshot() ? [{}] : [], subscribe: (_name: string, listener: () => void) => directory.subscribe(listener) }) const locale = new LocaleRuntime(ctx) @@ -58,7 +61,7 @@ async function bench(runtime: 'web' | 'desktop' = 'desktop') { const select = (id: string) => { list.set({ ...list.getSnapshot(), byId: { [sid('a')]: row('a', id === 'a'), [sid('b')]: row('b', id === 'b'), } }) } - return { ctx, fiber, registry, commands, navigation, controls, list, directory, select, history, loadOlder } + return { ctx, fiber, registry, commands, navigation, controls, list, directory, select, history, loadOlder, panelInfo } } afterEach(() => { vi.restoreAllMocks() }) @@ -82,12 +85,46 @@ describe('workspace shortcut ownership', () => { expect(b.registry.catalog.getSnapshot().every(row => row.keys.length > 0)).toBe(true) const web = await bench('web') expect(web.registry.catalog.getSnapshot().map(row => row.aria)).toEqual([ - 'Alt+Meta+N', 'Alt+Meta+K', 'Alt+Meta+O', 'Shift+Meta+R', 'Shift+Meta+F', 'Alt+Meta+A', + 'Alt+Meta+N', 'Alt+Meta+K', 'Alt+Meta+O', 'Alt+Meta+G', 'Shift+Meta+F', 'Alt+Meta+A', ]) await b.fiber.dispose() expect(b.registry.catalog.getSnapshot()).toEqual([]) }) + it.each([ + { runtime: 'web', platform: 'macos' }, { runtime: 'web', platform: 'windows' }, + { runtime: 'desktop', platform: 'macos' }, { runtime: 'desktop', platform: 'windows' }, + ] as const)('uses the shared three-key rename binding on $runtime/$platform', async ({ runtime, platform }) => { + const b = await bench(runtime, platform) + const primary = { meta: platform === 'macos', control: platform === 'windows' } + const consume = vi.fn() + for (const shift of [false, true]) { + expect(b.registry.dispatch(key('KeyR', { ...primary, shift }), context, consume)).toEqual({ status: 'pass' }) + } + expect(consume).not.toHaveBeenCalled() + expect(b.controls.state.getSnapshot().renameTarget).toBeNull() + expect(b.registry.dispatch(key('KeyG', { ...primary, alt: true }), context, consume).status).toBe('handled') + expect(b.controls.state.getSnapshot().renameTarget?.sessionId).toBe('a') + }) + + it.each(['web', 'desktop'] as const)('renames only a nonblank main Conversation without an obscuring modal on %s', async (runtime) => { + const b = await bench(runtime) + const gesture = key('KeyG', { alt: true }) + const invoke = (modal: string | null = null) => b.registry.dispatch(gesture, { ...context, modal }, vi.fn()) + b.list.set({ ...b.list.getSnapshot(), byId: { [sid('a')]: { ...row('a', true), blank: true } } }) + expect(invoke().status).toBe('blocked') + expect(b.controls.state.getSnapshot().renameTarget).toBeNull() + b.select('a') + b.panelInfo.set({ activePanelId: 'plugins' as MainPanelId }) + expect(invoke().status).toBe('blocked') + expect(b.controls.state.getSnapshot().renameTarget).toBeNull() + b.panelInfo.set({ activePanelId: null }) + expect(invoke('settings').status).toBe('blocked') + expect(b.controls.state.getSnapshot().renameTarget).toBeNull() + expect(invoke().status).toBe('handled') + expect(b.controls.state.getSnapshot().renameTarget?.sessionId).toBe('a') + }) + it('uses the owner for new/search/add from terminals and modals while preserving repeat and directory occupancy', async () => { const b = await bench() const consume = vi.fn() diff --git a/packages/extensions/ui-cordis/src/client/CordisDefineRow.module.css b/packages/extensions/ui-cordis/src/client/CordisDefineRow.module.css index c74fdc2654..ca9e03ae14 100644 --- a/packages/extensions/ui-cordis/src/client/CordisDefineRow.module.css +++ b/packages/extensions/ui-cordis/src/client/CordisDefineRow.module.css @@ -7,7 +7,7 @@ flex-direction: column; } -/* The shared cordis accent (matches ui-tool's [data-tool^='cordis_'] rows). */ +/* Definition cards keep a brand accent to distinguish their editable source. */ .card .title, .card .chevron { color: var(--dsw-alias-state-business-primary); diff --git a/snapshots/web/auto-review-approval/ui.expected.md b/snapshots/web/auto-review-approval/ui.expected.md index 004937af06..71cc3eb2b2 100644 --- a/snapshots/web/auto-review-approval/ui.expected.md +++ b/snapshots/web/auto-review-approval/ui.expected.md @@ -9,8 +9,8 @@ - tab "Trajectory" - text: Clean up the stale build log. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Called tools" [expanded] - text: Failed - 'button "Tool call Error: the user rejected tool \"mystery\""' @@ -36,8 +36,8 @@ - tab "Trajectory" - text: Clean up the stale build log. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Called tools" [expanded] - text: Failed - 'button "Tool call Error: the user rejected tool \"mystery\"" [expanded]' diff --git a/snapshots/web/auto-review-denial/ui.expected.md b/snapshots/web/auto-review-denial/ui.expected.md index 1ceeeb4f1e..d3172476e3 100644 --- a/snapshots/web/auto-review-denial/ui.expected.md +++ b/snapshots/web/auto-review-denial/ui.expected.md @@ -9,8 +9,8 @@ - tab "Trajectory" - text: Inspect the protected operation, but do not run it unless authorized. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Called tools, ran code, ran commands" [expanded] - text: Failed - button "Tool call Rejected by Auto review" @@ -37,8 +37,8 @@ - tab "Trajectory" - text: Inspect the protected operation, but do not run it unless authorized. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Called tools, ran code, ran commands" [expanded] - text: Failed - button "Tool call Rejected by Auto review" [expanded] diff --git a/snapshots/web/changed-files-turn/ui.expected.md b/snapshots/web/changed-files-turn/ui.expected.md index 61b2cdc9a2..0c2bcb603b 100644 --- a/snapshots/web/changed-files-turn/ui.expected.md +++ b/snapshots/web/changed-files-turn/ui.expected.md @@ -1,7 +1,7 @@ - text: 不用先查看目录,直接做四件事:把 intro.md 里的标题「示例项目」改成「项目说明」,新建 src/util.ts 导出一个两数相加的 add 函数,新建 app.local 写一行 mode=demo,最后用 bash 在 notes.txt 末尾追加一行 done。 {{clock}} - button "复制" -- status: 已完成工作 -- button "用时 {{duration}}" [expanded] +- status: 已完成 +- button "已完成,用时 {{duration}}" [expanded] - button "执行了命令,已写入文件,已读取文件等" [expanded] - button "思考 The user wants four things done directly. But I need to read intro.md before editing (fs-observation-policy requires reading existing file first). The user says don't look at the directory first, but reading intro.md is needed for the edit tool. Let me just do it." - button "读取 intro.md": diff --git a/snapshots/web/deepseek-messages-chat/ui.expected.md b/snapshots/web/deepseek-messages-chat/ui.expected.md index 021809ac0c..56f27b62ba 100644 --- a/snapshots/web/deepseek-messages-chat/ui.expected.md +++ b/snapshots/web/deepseek-messages-chat/ui.expected.md @@ -8,8 +8,8 @@ - tab "轨迹" - text: 只回复 MESSAGES_WEB_READY,不调用工具。 {{clock}} - button "复制" -- status: 已完成工作 -- button "用时 {{duration}}" [disabled] +- status: 已完成 +- button "已完成,用时 {{duration}}" [disabled] - paragraph: MESSAGES_WEB_READY - button "复制" - button "好的回答" diff --git a/snapshots/web/default-workspace/ui.expected.md b/snapshots/web/default-workspace/ui.expected.md index 27cdaa5150..9f26daff62 100644 --- a/snapshots/web/default-workspace/ui.expected.md +++ b/snapshots/web/default-workspace/ui.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: "Use the bash tool to run exactly: echo WEB_E2E_OK. Then reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: DONE - button "Copy" - button "Good response" diff --git a/snapshots/web/feedback-command/ack-expanded.expected.md b/snapshots/web/feedback-command/ack-expanded.expected.md index 10e74c04ce..de8b562f3c 100644 --- a/snapshots/web/feedback-command/ack-expanded.expected.md +++ b/snapshots/web/feedback-command/ack-expanded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Reply with the single word LIGHTHOUSE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Analysis completed" [expanded] - button "Think The user wants me to reply with a single word. Let me comply." - paragraph: LIGHTHOUSE diff --git a/snapshots/web/feedback-command/ack.expected.md b/snapshots/web/feedback-command/ack.expected.md index 716f3bd304..4f55e3e95f 100644 --- a/snapshots/web/feedback-command/ack.expected.md +++ b/snapshots/web/feedback-command/ack.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Reply with the single word LIGHTHOUSE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: LIGHTHOUSE - button "Copy" - button "Good response" diff --git a/snapshots/web/feedback-release/ack-expanded.expected.md b/snapshots/web/feedback-release/ack-expanded.expected.md index 10e74c04ce..de8b562f3c 100644 --- a/snapshots/web/feedback-release/ack-expanded.expected.md +++ b/snapshots/web/feedback-release/ack-expanded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Reply with the single word LIGHTHOUSE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Analysis completed" [expanded] - button "Think The user wants me to reply with a single word. Let me comply." - paragraph: LIGHTHOUSE diff --git a/snapshots/web/feedback-release/ack.expected.md b/snapshots/web/feedback-release/ack.expected.md index 716f3bd304..4f55e3e95f 100644 --- a/snapshots/web/feedback-release/ack.expected.md +++ b/snapshots/web/feedback-release/ack.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Reply with the single word LIGHTHOUSE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: LIGHTHOUSE - button "Copy" - button "Good response" diff --git a/snapshots/web/file-upload-round/ui.expected.md b/snapshots/web/file-upload-round/ui.expected.md index 0c60a10a69..ff7effe13d 100644 --- a/snapshots/web/file-upload-round/ui.expected.md +++ b/snapshots/web/file-upload-round/ui.expected.md @@ -11,8 +11,8 @@ - img "reference-1.png" - text: Read the attached file with the read tool, reply with exactly the single word it contains, and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: UPLOAD_ROUND_OK - button "Copy" - button "Good response" diff --git a/snapshots/web/fresh-round-trip/ui-expanded.expected.md b/snapshots/web/fresh-round-trip/ui-expanded.expected.md index e277911d98..3d1d79d896 100644 --- a/snapshots/web/fresh-round-trip/ui-expanded.expected.md +++ b/snapshots/web/fresh-round-trip/ui-expanded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: "Use the bash tool to run exactly: echo WEB_E2E_OK. Then reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Ran commands" [expanded] - button "Think The user wants me to run a simple bash command and reply with \"DONE\"." - button "Bash Echo the test string" diff --git a/snapshots/web/fresh-round-trip/ui.expected.md b/snapshots/web/fresh-round-trip/ui.expected.md index 27cdaa5150..9f26daff62 100644 --- a/snapshots/web/fresh-round-trip/ui.expected.md +++ b/snapshots/web/fresh-round-trip/ui.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: "Use the bash tool to run exactly: echo WEB_E2E_OK. Then reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: DONE - button "Copy" - button "Good response" diff --git a/snapshots/web/goal-multi-turn-actions/ui-expanded.expected.md b/snapshots/web/goal-multi-turn-actions/ui-expanded.expected.md index e2676d0594..f1729da55e 100644 --- a/snapshots/web/goal-multi-turn-actions/ui-expanded.expected.md +++ b/snapshots/web/goal-multi-turn-actions/ui-expanded.expected.md @@ -14,8 +14,8 @@ - button "Continuing goal {{clock}}": - text: Continuing goal - time: {{clock}} -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Ran commands and updated the plan" [expanded] - 'button "Think The user''s goal is: \"做两个turn,每个turn输出随机一个包的文件结构。注意你做完一个turn之后,直接输出内容,停止,我们的系统会帮你再开一个turn,你看着做一个类似的\" — which translates to: \"Do two turns, each turn output the file structure of a random package. Note that after you finish one turn, directly output the content and stop; our system will help you open another turn, and you just do something similar.\""' - button "Bash List package directories deterministically" @@ -51,8 +51,8 @@ - button "Continuing goal {{clock}}": - text: Continuing goal - time: {{clock}} -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Ran commands" [expanded] - 'button "Think This is turn 2 of 2. I need to randomly pick another package and output its file structure. In the first turn I picked `packages/context/session-reference`. Let me pick a different one randomly this time."' - button "Bash Randomly select another package with Python" diff --git a/snapshots/web/goal-multi-turn-actions/ui.expected.md b/snapshots/web/goal-multi-turn-actions/ui.expected.md index d7d9ad4839..505a067cbb 100644 --- a/snapshots/web/goal-multi-turn-actions/ui.expected.md +++ b/snapshots/web/goal-multi-turn-actions/ui.expected.md @@ -14,8 +14,8 @@ - button "Continuing goal {{clock}}": - text: Continuing goal - time: {{clock}} -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: - strong: Turn 1 / 2 - text: — 随机选中的包: @@ -38,8 +38,8 @@ - button "Continuing goal {{clock}}": - text: Continuing goal - time: {{clock}} -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: 两个 turn 都完成了 ✅ - paragraph: - strong: 总结: @@ -94,7 +94,6 @@ - button "Good response" - button "Bad response" - button "Branch into a new conversation" -- tooltip "Branch into a new conversation" - text: {{clock}} - textbox "Message or run a task, / commands, @ files or sessions" - button "Add files or run commands" diff --git a/snapshots/web/lifecycle-chrome/reloaded-expanded.expected.md b/snapshots/web/lifecycle-chrome/reloaded-expanded.expected.md index d58fce245c..b65881f086 100644 --- a/snapshots/web/lifecycle-chrome/reloaded-expanded.expected.md +++ b/snapshots/web/lifecycle-chrome/reloaded-expanded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Reply with the single word LIGHTHOUSE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Analysis completed" [expanded] - button "Think The user wants me to reply with a single word. Let me comply." - paragraph: LIGHTHOUSE diff --git a/snapshots/web/lifecycle-chrome/reloaded.expected.md b/snapshots/web/lifecycle-chrome/reloaded.expected.md index f6a61355d8..6f51412377 100644 --- a/snapshots/web/lifecycle-chrome/reloaded.expected.md +++ b/snapshots/web/lifecycle-chrome/reloaded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Reply with the single word LIGHTHOUSE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: LIGHTHOUSE - button "Copy" - button "Good response" diff --git a/snapshots/web/live-interactions/loading.expected.md b/snapshots/web/live-interactions/loading.expected.md index ba0befdc1c..2014c5eae7 100644 --- a/snapshots/web/live-interactions/loading.expected.md +++ b/snapshots/web/live-interactions/loading.expected.md @@ -9,8 +9,8 @@ - text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} - button "Copy" - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - textbox "Message or run a task, / commands, @ files or sessions" - button "Add files or run commands" - 'button "Access mode, current: Workspace Write"': Workspace Write diff --git a/snapshots/web/live-interactions/retry-expanded.expected.md b/snapshots/web/live-interactions/retry-expanded.expected.md index 672e1c5b05..87986d5082 100644 --- a/snapshots/web/live-interactions/retry-expanded.expected.md +++ b/snapshots/web/live-interactions/retry-expanded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - group: - status: Retried model request (1/5) · {{duration}} - button "Analysis completed" [expanded] diff --git a/snapshots/web/live-interactions/retry.expected.md b/snapshots/web/live-interactions/retry.expected.md index 33c058c865..abc388a29c 100644 --- a/snapshots/web/live-interactions/retry.expected.md +++ b/snapshots/web/live-interactions/retry.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. - button "Copy" - button "Good response" diff --git a/snapshots/web/live-interactions/running-draft.expected.md b/snapshots/web/live-interactions/running-draft.expected.md index 21bc5071dd..001297a018 100644 --- a/snapshots/web/live-interactions/running-draft.expected.md +++ b/snapshots/web/live-interactions/running-draft.expected.md @@ -9,8 +9,8 @@ - text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} - button "Copy" - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - textbox "Message or run a task, / commands, @ files or sessions": - paragraph: Queue this follow-up while the current turn is running. - button "Add files or run commands" diff --git a/snapshots/web/message-actions/ui.expected.md b/snapshots/web/message-actions/ui.expected.md index ed11835a6c..f1c18441de 100644 --- a/snapshots/web/message-actions/ui.expected.md +++ b/snapshots/web/message-actions/ui.expected.md @@ -23,8 +23,8 @@ - button "Branch into a new conversation" [disabled] - text: Available only on the last message of a completed turn {{clock}} Now give the final answer. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: DONE - button "Copy" - button "Good response" @@ -32,8 +32,8 @@ - button "Branch into a new conversation" - text: {{clock}} Keep this later input in the original conversation. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - paragraph: ORIGINAL ONLY - button "Copy" - button "Good response" diff --git a/snapshots/web/minimal-preset/ui.expected.md b/snapshots/web/minimal-preset/ui.expected.md index e489e916e6..0469ba0e75 100644 --- a/snapshots/web/minimal-preset/ui.expected.md +++ b/snapshots/web/minimal-preset/ui.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: "Use the bash tool to run exactly: printf 'MINIMAL_BASH_CARD_OK\\n'. Then reply exactly MINIMAL_PRESET_REQUEST_OK and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Ran commands" [expanded] - button "Bash printf 'MINIMAL_BASH_CARD_OK\\n'" [expanded] - text: "IN { \"command\": \"printf 'MINIMAL_BASH_CARD_OK\\\\n'\" } OUT MINIMAL_BASH_CARD_OK [Command finished with exit code 0]" diff --git a/snapshots/web/plan-review/approved-expanded.expected.md b/snapshots/web/plan-review/approved-expanded.expected.md index 23a36d6ec3..a9ab6bccfa 100644 --- a/snapshots/web/plan-review/approved-expanded.expected.md +++ b/snapshots/web/plan-review/approved-expanded.expected.md @@ -7,8 +7,8 @@ - tab "Trajectory" - text: "plan Plan mode on. Use /plan off to leave. Plan a small change: add a --greeting flag to a CLI. Do not read or write any files. Call exit_plan_mode with a short plan of at most five bullet points. Once the plan is approved, reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Analysis completed" [expanded] - 'button "Think The user wants me to plan a small change to add a `--greeting` flag to a CLI. They explicitly told me not to read or write any files, and to call exit_plan_mode with a short plan. Let me do that directly."' - paragraph: diff --git a/snapshots/web/plan-review/approved.expected.md b/snapshots/web/plan-review/approved.expected.md index 279843a249..bab8040ae6 100644 --- a/snapshots/web/plan-review/approved.expected.md +++ b/snapshots/web/plan-review/approved.expected.md @@ -7,8 +7,8 @@ - tab "Trajectory" - text: "plan Plan mode on. Use /plan off to leave. Plan a small change: add a --greeting flag to a CLI. Do not read or write any files. Call exit_plan_mode with a short plan of at most five bullet points. Once the plan is approved, reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: DONE - 'button "Open plan: Add `--greeting` flag to CLI"': "Add `--greeting` flag to CLI Plan · Markdown Open" - button "Copy" diff --git a/snapshots/web/present-svg/ui.expected.md b/snapshots/web/present-svg/ui.expected.md index 5172b9d518..304d41f506 100644 --- a/snapshots/web/present-svg/ui.expected.md +++ b/snapshots/web/present-svg/ui.expected.md @@ -1,7 +1,7 @@ - text: 简单画一个 SVG 表示冯诺依曼架构, 保存为 von-neumann.svg {{clock}} - button "复制" -- status: 已完成工作 -- button "用时 {{duration}}" [expanded] +- status: 已完成 +- button "已完成,用时 {{duration}}" [expanded] - button "已完成分析" [expanded] - button "思考 The user wants a simple SVG diagram of the von Neumann architecture, saved as von-neumann.svg. Let me create a clean SVG showing CPU (control unit, ALU), memory, input, and output with bidirectional data/control buses." - paragraph: 我来创建一个冯诺依曼架构的 SVG 示意图。 diff --git a/snapshots/web/present/ui.expected.md b/snapshots/web/present/ui.expected.md index 63418d2f53..0f99b63f90 100644 --- a/snapshots/web/present/ui.expected.md +++ b/snapshots/web/present/ui.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: "Use one run_code program to do the following in order. Call present for missing.txt and catch its error without creating that file. Use bash to run exactly `printf \"DELIVERED_REPORT\\n\" > report.txt; printf \"DELIVERED_NOTE\\n\" > 说明.txt`. Call present for report.txt and 说明.txt. After present succeeds, deliberately throw the string \"AFTER_PRESENT\" (not an Error object) from that same run_code program. Do not retry the program or create any other files. Finish by mentioning `report.txt` and `说明.txt` in inline code, and put PRESENT_DONE in a separate paragraph. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Called tools, ran code, ran commands" [expanded] - button "Think The user wants one run_code program that:" - text: Failed diff --git a/snapshots/web/ptc-round/ui.expected.md b/snapshots/web/ptc-round/ui.expected.md index 873cf43d8d..533cca0451 100644 --- a/snapshots/web/ptc-round/ui.expected.md +++ b/snapshots/web/ptc-round/ui.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: "Using ONE run_code program: run bash `echo CODE_ROUND_OK`, then read the file missing.txt catching its error in the program. Return an object with both outcomes. Then reply DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Ran code, ran commands, read files" [expanded] - 'button "Think The user wants me to write a single `run_code` program that:"' - button "Code Run bash echo and catch missing file read" diff --git a/snapshots/web/question-composer/answered-expanded.expected.md b/snapshots/web/question-composer/answered-expanded.expected.md index 01d7ae2f94..ac025f1c8d 100644 --- a/snapshots/web/question-composer/answered-expanded.expected.md +++ b/snapshots/web/question-composer/answered-expanded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: "Use the ask_user_question tool to ask me exactly one multi-select question with id \"color\", question \"Which color do you prefer?\", header \"Pick one\", and two options: label \"Blue\" with description \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\", and label \"Green\" with description \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\" Set multi_select to true. After I answer, reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Asked questions" [expanded] - button "Think The user wants me to use the ask_user_question tool with specific parameters. Let me do exactly that." - button "Ask question 1/1 answered" [expanded] diff --git a/snapshots/web/question-composer/answered.expected.md b/snapshots/web/question-composer/answered.expected.md index be7b65aa37..6b62b305cb 100644 --- a/snapshots/web/question-composer/answered.expected.md +++ b/snapshots/web/question-composer/answered.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: "Use the ask_user_question tool to ask me exactly one multi-select question with id \"color\", question \"Which color do you prefer?\", header \"Pick one\", and two options: label \"Blue\" with description \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\", and label \"Green\" with description \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\" Set multi_select to true. After I answer, reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: DONE - button "Copy" - button "Good response" diff --git a/snapshots/web/question-composer/cancelled.expected.md b/snapshots/web/question-composer/cancelled.expected.md index 855fb34837..2659625d20 100644 --- a/snapshots/web/question-composer/cancelled.expected.md +++ b/snapshots/web/question-composer/cancelled.expected.md @@ -7,8 +7,8 @@ - tab "Trajectory" - text: "Use the ask_user_question tool to ask me exactly one multi-select question with id \"color\", question \"Which color do you prefer?\", header \"Pick one\", and two options: label \"Blue\" with description \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\", and label \"Green\" with description \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\" Set multi_select to true. After I answer, reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Asked questions" [expanded] - button "Think The user wants me to use the ask_user_question tool with specific parameters. Let me do exactly that." - button "Ask question cancelled" [expanded] diff --git a/snapshots/web/queue-actions/collapsed.expected.md b/snapshots/web/queue-actions/collapsed.expected.md index fde9a3f1f3..bdbe26643f 100644 --- a/snapshots/web/queue-actions/collapsed.expected.md +++ b/snapshots/web/queue-actions/collapsed.expected.md @@ -9,8 +9,8 @@ - text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} - button "Copy" - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - button "2 queued messages" - textbox "Cmd/Ctrl+Enter steers all queued messages" - button "Add files or run commands" diff --git a/snapshots/web/queue-actions/editing.expected.md b/snapshots/web/queue-actions/editing.expected.md index d437dfe4d6..1356f594be 100644 --- a/snapshots/web/queue-actions/editing.expected.md +++ b/snapshots/web/queue-actions/editing.expected.md @@ -9,8 +9,8 @@ - text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} - button "Copy" - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - button "2 queued messages" [disabled] [expanded] - list: - listitem: diff --git a/snapshots/web/queue-actions/layout.expected.md b/snapshots/web/queue-actions/layout.expected.md index 69dcd81aeb..83582f34b3 100644 --- a/snapshots/web/queue-actions/layout.expected.md +++ b/snapshots/web/queue-actions/layout.expected.md @@ -12,8 +12,8 @@ - text: Continuing goal - time: {{clock}} - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - region "To-dos": - button "To-dos 1 completed · 1 in progress" - text: Ongoing Goal Keep the composer context panels aligned diff --git a/snapshots/web/queue-actions/sending.expected.md b/snapshots/web/queue-actions/sending.expected.md index 7941e72458..afbcc84e3a 100644 --- a/snapshots/web/queue-actions/sending.expected.md +++ b/snapshots/web/queue-actions/sending.expected.md @@ -9,8 +9,8 @@ - text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} - button "Copy" - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - list: - listitem: - text: Queue item to remove diff --git a/snapshots/web/queue-actions/ui.expected.md b/snapshots/web/queue-actions/ui.expected.md index da6d7a2135..90757b54c3 100644 --- a/snapshots/web/queue-actions/ui.expected.md +++ b/snapshots/web/queue-actions/ui.expected.md @@ -9,8 +9,8 @@ - text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} - button "Copy" - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - list: - listitem: - text: Edited queue item diff --git a/snapshots/web/queued-image/delivered.expected.md b/snapshots/web/queued-image/delivered.expected.md index d014db4b09..7d06bd0b91 100644 --- a/snapshots/web/queued-image/delivered.expected.md +++ b/snapshots/web/queued-image/delivered.expected.md @@ -25,8 +25,8 @@ - img "queued.png" - text: Compare with this screenshot {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. - button "Copy" - button "Good response" @@ -34,8 +34,8 @@ - button "Branch into a new conversation" - text: {{clock}} Continue with the queued comparison {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. - button "Copy" - button "Good response" diff --git a/snapshots/web/queued-image/queued.expected.md b/snapshots/web/queued-image/queued.expected.md index 510fc23dbd..deb90f70f4 100644 --- a/snapshots/web/queued-image/queued.expected.md +++ b/snapshots/web/queued-image/queued.expected.md @@ -9,8 +9,8 @@ - text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} - button "Copy" - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - list: - listitem: - img "Queued message image" diff --git a/snapshots/web/seeded-history/command-row.expected.md b/snapshots/web/seeded-history/command-row.expected.md index bbea1a9d41..fa8c467c97 100644 --- a/snapshots/web/seeded-history/command-row.expected.md +++ b/snapshots/web/seeded-history/command-row.expected.md @@ -10,8 +10,8 @@ - button "Jump to turn 2" - text: "Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Read files" [expanded] - button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel." - button "Read a.txt": @@ -28,8 +28,8 @@ - button "Branch into a new conversation" - text: {{clock}} - button "compact Compacted 5 history items (~{{tokens}} tokens)" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - textbox "Message or run a task, / commands, @ files or sessions" - button "Add files or run commands" - 'button "Access mode, current: Read Only"': Read Only diff --git a/snapshots/web/seeded-history/feedback-row.expected.md b/snapshots/web/seeded-history/feedback-row.expected.md index fa6a31d7ae..48d7e8baa8 100644 --- a/snapshots/web/seeded-history/feedback-row.expected.md +++ b/snapshots/web/seeded-history/feedback-row.expected.md @@ -10,8 +10,8 @@ - button "Jump to turn 2" - text: "Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Read files" [expanded] - button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel." - button "Read a.txt": @@ -28,8 +28,8 @@ - button "Branch into a new conversation" - text: {{clock}} - button "compact Compacted 5 history items (~{{tokens}} tokens)" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - 'button "feedback Feedback recorded for session {{seededId}} Anonymous user: {{uuid}}." [expanded]' - text: "Feedback recorded for session {{seededId}} Anonymous user: {{uuid}}." - textbox "Message or run a task, / commands, @ files or sessions" diff --git a/snapshots/web/seeded-history/ui-expanded.expected.md b/snapshots/web/seeded-history/ui-expanded.expected.md index 8623e4329b..ccb4d8a3b1 100644 --- a/snapshots/web/seeded-history/ui-expanded.expected.md +++ b/snapshots/web/seeded-history/ui-expanded.expected.md @@ -10,8 +10,8 @@ - button "Jump to turn 2" - text: "Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Read files" [expanded] - button "Think The user wants me to read a.txt and b.txt, then reply with \"DONE\". Let me do both reads in parallel." - button "Read a.txt": @@ -28,8 +28,8 @@ - button "Branch into a new conversation" - text: {{clock}} - button "compact Compacted 5 history items (~{{tokens}} tokens)" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - textbox "Message or run a task, / commands, @ files or sessions" - button "Add files or run commands" - 'button "Access mode, current: Workspace Write"': Workspace Write diff --git a/snapshots/web/seeded-history/ui.expected.md b/snapshots/web/seeded-history/ui.expected.md index b1842edc41..d2c53e99b2 100644 --- a/snapshots/web/seeded-history/ui.expected.md +++ b/snapshots/web/seeded-history/ui.expected.md @@ -10,8 +10,8 @@ - button "Jump to turn 2" - text: "Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: DONE - button "Copy" - button "Good response" @@ -19,8 +19,8 @@ - button "Branch into a new conversation" - text: {{clock}} - button "compact Compacted 5 history items (~{{tokens}} tokens)" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - textbox "Message or run a task, / commands, @ files or sessions" - button "Add files or run commands" - 'button "Access mode, current: Workspace Write"': Workspace Write diff --git a/snapshots/web/skill-tool-row/ui.expected.md b/snapshots/web/skill-tool-row/ui.expected.md index ea40915240..5149a5d21a 100644 --- a/snapshots/web/skill-tool-row/ui.expected.md +++ b/snapshots/web/skill-tool-row/ui.expected.md @@ -7,8 +7,8 @@ - tab "Trajectory" - text: Load the editing-cordis-compositions skill with the skill tool, then reply DONE. {{date}} {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Called tools" [expanded] - button "Think Load the requested skill." - button "Skill editing-cordis-compositions" [expanded] diff --git a/snapshots/web/steering/mid-steer.expected.md b/snapshots/web/steering/mid-steer.expected.md index 3fbe1ef4be..7ff5fd2d8b 100644 --- a/snapshots/web/steering/mid-steer.expected.md +++ b/snapshots/web/steering/mid-steer.expected.md @@ -12,8 +12,8 @@ - button "Ask question waiting" - text: "Interjection: include the word BANANA in your final reply." - button "Copy" -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - region "Ready to continue?": - text: Checkpoint - heading "Ready to continue?" [level=2] diff --git a/snapshots/web/steering/settled-expanded.expected.md b/snapshots/web/steering/settled-expanded.expected.md index b04beec4bb..6ccbadfd62 100644 --- a/snapshots/web/steering/settled-expanded.expected.md +++ b/snapshots/web/steering/settled-expanded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Use the ask_user_question tool to ask me exactly one question with id "checkpoint", question "Ready to continue?", header "Checkpoint", and options labeled "Yes" and "No". After I answer, reply with one short sentence acknowledging my answer and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - button "Asked questions" [expanded] - button "Ask question 1/1 answered" - text: "Interjection: include the word BANANA in your final reply. {{clock}}" diff --git a/snapshots/web/steering/settled.expected.md b/snapshots/web/steering/settled.expected.md index 030841b7e4..b13cb9a2f9 100644 --- a/snapshots/web/steering/settled.expected.md +++ b/snapshots/web/steering/settled.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Use the ask_user_question tool to ask me exactly one question with id "checkpoint", question "Ready to continue?", header "Checkpoint", and options labeled "Yes" and "No". After I answer, reply with one short sentence acknowledging my answer and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - button "Asked questions" - text: "Interjection: include the word BANANA in your final reply. {{clock}}" - button "Copy" diff --git a/snapshots/web/subagent-conversation/nested.expected.md b/snapshots/web/subagent-conversation/nested.expected.md index c04320b0b6..01e95cd04d 100644 --- a/snapshots/web/subagent-conversation/nested.expected.md +++ b/snapshots/web/subagent-conversation/nested.expected.md @@ -12,8 +12,8 @@ - tab "Trajectory" - text: Give one concrete event sourcing example. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [disabled] +- status: Completed +- button "Completed in {{duration}}" [disabled] - status: - strong: This subagent is read-only for now - text: The parent session is offline; reopen it to continue sending messages. diff --git a/snapshots/web/subagent-conversation/sidebar-chat.expected.md b/snapshots/web/subagent-conversation/sidebar-chat.expected.md index 607e142b62..748cd0db25 100644 --- a/snapshots/web/subagent-conversation/sidebar-chat.expected.md +++ b/snapshots/web/subagent-conversation/sidebar-chat.expected.md @@ -1,7 +1,7 @@ - text: "Explain event sourcing in one sentence.Your parent agent id is \"session-{{uuid}}\". Before you finish, send your result to that agent with send_message({ agent_id: \"session-{{uuid}}\", message: \"\" }). The parent shares your workspace but does not automatically receive your transcript, tool output, or reasoning. Send earlier messages as well when a finding changes what the parent should do next; sending a message does not end your turn. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. - button "Copy" - button "Good response" diff --git a/snapshots/web/subagent-conversation/ui-expanded.expected.md b/snapshots/web/subagent-conversation/ui-expanded.expected.md index 4fa5dffd27..73e6b048d0 100644 --- a/snapshots/web/subagent-conversation/ui-expanded.expected.md +++ b/snapshots/web/subagent-conversation/ui-expanded.expected.md @@ -15,8 +15,8 @@ - button "Jump to turn 2" - text: "Explain event sourcing in one sentence.Your parent agent id is \"session-{{uuid}}\". Before you finish, send your result to that agent with send_message({ agent_id: \"session-{{uuid}}\", message: \"\" }). The parent shares your workspace but does not automatically receive your transcript, tool output, or reasoning. Send earlier messages as well when a finding changes what the parent should do next; sending a message does not end your turn. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Analysis completed" [expanded] - button "Think The user is asking for a one-sentence description of event sourcing. This is a straightforward knowledge question that doesn't require any skill loading or tool calls." - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. @@ -26,8 +26,8 @@ - button "Branch into a new conversation" - text: {{clock}} Now give the same explanation to a human reader. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Analysis completed" [expanded] - button "Think The user is asking for a one-sentence description of event sourcing. This is a straightforward knowledge question that doesn't require any skill loading or tool calls." - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. diff --git a/snapshots/web/subagent-conversation/ui.expected.md b/snapshots/web/subagent-conversation/ui.expected.md index 6e67c79557..fd6280355e 100644 --- a/snapshots/web/subagent-conversation/ui.expected.md +++ b/snapshots/web/subagent-conversation/ui.expected.md @@ -15,8 +15,8 @@ - button "Jump to turn 2" - text: "Explain event sourcing in one sentence.Your parent agent id is \"session-{{uuid}}\". Before you finish, send your result to that agent with send_message({ agent_id: \"session-{{uuid}}\", message: \"\" }). The parent shares your workspace but does not automatically receive your transcript, tool output, or reasoning. Send earlier messages as well when a finding changes what the parent should do next; sending a message does not end your turn. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. - button "Copy" - button "Good response" @@ -24,8 +24,8 @@ - button "Branch into a new conversation" - text: {{clock}} Now give the same explanation to a human reader. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. - button "Copy" - button "Good response" diff --git a/snapshots/web/subagent-interrupt/offline-composer.expected.md b/snapshots/web/subagent-interrupt/offline-composer.expected.md index 0f176ad891..46af81e085 100644 --- a/snapshots/web/subagent-interrupt/offline-composer.expected.md +++ b/snapshots/web/subagent-interrupt/offline-composer.expected.md @@ -12,8 +12,8 @@ - text: "Explain event sourcing in one sentence.Your parent agent id is \"session-{{uuid}}\". Before you finish, send your result to that agent with send_message({ agent_id: \"session-{{uuid}}\", message: \"\" }). The parent shares your workspace but does not automatically receive your transcript, tool output, or reasoning. Send earlier messages as well when a finding changes what the parent should do next; sending a message does not end your turn. {{clock}}" - button "Copy" - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - list: - listitem: - text: Keep working until I stop you again. diff --git a/snapshots/web/turn-tail-actions/completed.expected.md b/snapshots/web/turn-tail-actions/completed.expected.md index 75ee221050..5d309edf46 100644 --- a/snapshots/web/turn-tail-actions/completed.expected.md +++ b/snapshots/web/turn-tail-actions/completed.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Begin your reply with the plain sentence "Reading the workspace now." as text, and in that same message call the bash tool with the command "echo alpha". After the tool result, reply with the single word DONE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: DONE - button "Copy" - button "Good response" diff --git a/snapshots/web/turn-tail-actions/focused.expected.md b/snapshots/web/turn-tail-actions/focused.expected.md index a31f0d0c79..353f648456 100644 --- a/snapshots/web/turn-tail-actions/focused.expected.md +++ b/snapshots/web/turn-tail-actions/focused.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Begin your reply with the plain sentence "Reading the workspace now." as text, and in that same message call the bash tool with the command "echo alpha". After the tool result, reply with the single word DONE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Analysis completed" - paragraph: Reading the workspace now. - button "Ran commands" [expanded] diff --git a/snapshots/web/turn-tail-actions/running.expected.md b/snapshots/web/turn-tail-actions/running.expected.md index 93bf45f8dd..c9b5ddf340 100644 --- a/snapshots/web/turn-tail-actions/running.expected.md +++ b/snapshots/web/turn-tail-actions/running.expected.md @@ -13,8 +13,8 @@ - paragraph: Reading the workspace now. - button "Bash Print alpha to stdout" - paragraph: partial -- status: Deep diving... -- text: Deep diving for {{duration}}... +- status: Deep diving +- text: Deep diving for {{duration}} ··· - textbox "Message or run a task, / commands, @ files or sessions" - button "Add files or run commands" - 'button "Access mode, current: Workspace Write"': Workspace Write diff --git a/snapshots/web/turn-tail-actions/usage-expanded.expected.md b/snapshots/web/turn-tail-actions/usage-expanded.expected.md index 8ced72edd1..791695b15c 100644 --- a/snapshots/web/turn-tail-actions/usage-expanded.expected.md +++ b/snapshots/web/turn-tail-actions/usage-expanded.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Begin your reply with the plain sentence "Reading the workspace now." as text, and in that same message call the bash tool with the command "echo alpha". After the tool result, reply with the single word DONE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" +- status: Completed +- button "Completed in {{duration}}" - paragraph: DONE - button "Copy" - button "Good response" diff --git a/snapshots/web/web-search-round/ui.expected.md b/snapshots/web/web-search-round/ui.expected.md index f229309b8c..92cc1ce25e 100644 --- a/snapshots/web/web-search-round/ui.expected.md +++ b/snapshots/web/web-search-round/ui.expected.md @@ -8,8 +8,8 @@ - tab "Trajectory" - text: Use web_search once with queries ["DeepSeek Harness snapshot search","DeepSeek Harness multi-query search"]. Then reply exactly SEARCH_DONE and stop. {{clock}} - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Searched the web" [expanded] - button "Search DeepSeek Harness snapshot search, DeepSeek Harness multi-query search" - paragraph: SEARCH_DONE diff --git a/snapshots/web/workflow-run/ui.expected.md b/snapshots/web/workflow-run/ui.expected.md index f0272f08d8..e037e0a6a4 100644 --- a/snapshots/web/workflow-run/ui.expected.md +++ b/snapshots/web/workflow-run/ui.expected.md @@ -1,7 +1,7 @@ - text: "Use the workflow tool exactly once, with args omitted, meta set to { \"name\": \"snapshot-flow\", \"description\": \"one child for the snapshot\" }, and this EXACT script body (copy it verbatim): phase('Run') const reply = await agent('Reply with exactly the word WF_CHILD_OK and nothing else.') return { reply } After the workflow returns, reply with the single word WORKFLOW_DONE and stop. Do not use any other tool. {{clock}}" - button "Copy" -- status: Worked -- button "Took {{duration}}" [expanded] +- status: Completed +- button "Completed in {{duration}}" [expanded] - button "Called tools" [expanded] - button "Think The user wants me to use the workflow tool exactly once with specific parameters. Let me carefully follow the instructions:" - button "Run workflow snapshot-flow"