From f9bac5a7b17b2e7bb16bf7cb74456244448f9be6 Mon Sep 17 00:00:00 2001 From: vastsa Date: Wed, 16 Sep 2026 22:49:29 +0800 Subject: [PATCH] fix(asktool): paint the dock question card on the composer plate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #360 item 1 ("选择交互面板缺少背景色") reads as a panel with no background because the question card is mounted in the transparent composer dock (Composer.tsx renders it as a direct `.composer-stack` child) yet still painted the in-flow `--ds-tile` wash: a 3.5% ink mix with no shadow, measured at `#f7f7f7` on the reporter's light-theme screenshot. The dock rule's own comment already claimed the card "uses the same decision surface as Plan and Goal approval", while `.plan-approval-bar` in that exact slot paints `--ds-bg-composer` with `--ds-shadow-composer` (`04-ux/03-permission-ux.md` §9, `04-ux/08-component-spec.md` §11.5). - `.composer-stack > .asktool-card` now paints that plate in both palettes. - Its option rows and custom input move to the plate's inlaid tier (`--ds-tile-deep`, no raised shadow) — the layer `.plan-approval-split` already uses on that same plate — so the rows cannot be white on white in the light palette, where `--ds-raised` and `--ds-bg-composer` are both `#ffffff`. The 15 px option mark keeps `--ds-tile-deep`, the sidebar checkbox's mark. - Tokens only: a contributed theme moves the plate and the rows with `--ds-bg-composer` / `--ds-tile-deep`. No protocol, storage, host, permission, migration, or preference change, and no new default (D435). - E2E-078's Chromium probe now renders the dock card and asserts the built-in paint, the shadow, and the contributed-variable cascade in light and dark; `permission-inline.test.mjs` asserts the dock rule and the inlaid rows. Refs #360 --- apps/desktop/src/styles/composer.css | 10 +++++-- apps/desktop/src/styles/messages.css | 10 +++---- apps/desktop/test/permission-inline.test.mjs | 19 +++++++++++- docs/spec/04-ux/11-asktool-question-card.md | 11 ++++--- docs/spec/06-delivery/04-e2e-test-plan.md | 11 +++++-- docs/spec/08-meta/decisions-log.md | 30 +++++++++++++++++++ .../spec/04-ux/11-asktool-question-card.md | 10 ++++--- .../spec/06-delivery/04-e2e-test-plan.md | 9 ++++-- docs/zh-CN/spec/08-meta/decisions-log.md | 7 +++++ scripts/e2e-theme-surfaces.mjs | 1 + scripts/e2e/theme-surfaces.js | 17 +++++++++++ 11 files changed, 112 insertions(+), 23 deletions(-) diff --git a/apps/desktop/src/styles/composer.css b/apps/desktop/src/styles/composer.css index 39cddc5dce..bd6871f139 100644 --- a/apps/desktop/src/styles/composer.css +++ b/apps/desktop/src/styles/composer.css @@ -49,12 +49,16 @@ transition: width var(--chat-width-transition, var(--motion-duration-normal) var(--motion-ease-out)); } -/* Asktool uses the same decision surface as Plan and Goal approval. Its - * transcript card width is intentionally widened here to align with the - * composer stack instead of leaving an inset panel above the input. */ +/* Asktool uses the same decision surface as Plan and Goal approval: it is + * mounted in the transparent composer dock, so it paints the composer plate + * with its shadow rather than the in-flow `--ds-tile` wash (03-permission-ux + * §9). Its width is the composer stack's instead of an inset panel above the + * input. */ .composer-stack > .asktool-card { width: 100%; margin: 0 0 8px; + background: var(--ds-bg-composer); + box-shadow: var(--ds-shadow-composer); } .composer-queued-prompts { diff --git a/apps/desktop/src/styles/messages.css b/apps/desktop/src/styles/messages.css index 66c1638a64..53a899e103 100644 --- a/apps/desktop/src/styles/messages.css +++ b/apps/desktop/src/styles/messages.css @@ -166,6 +166,8 @@ scroll-padding-block: 2px; } +/* The card rides the composer plate below, so its option rows are inlaid fills + on that plate rather than the raised layer of an in-flow surface (D297). */ .asktool-option { display: flex; width: 100%; @@ -174,8 +176,7 @@ min-height: 30px; padding: 4px 8px; border-radius: var(--radius-sm); - background: var(--ds-raised); - box-shadow: var(--ds-raised-shadow); + background: var(--ds-tile-deep); color: var(--ds-text-secondary); cursor: pointer; font: inherit; @@ -186,7 +187,7 @@ .asktool-option:hover, .asktool-option.selected { - background: color-mix(in oklab, var(--ds-accent) 10%, var(--ds-raised)); + background: color-mix(in oklab, var(--ds-accent) 10%, var(--ds-tile-deep)); color: var(--ds-text-primary); } @@ -218,8 +219,7 @@ padding: 6px 8px; border: 0; border-radius: var(--radius-sm); - background: var(--ds-raised); - box-shadow: var(--ds-raised-shadow); + background: var(--ds-tile-deep); color: var(--ds-text-primary); font: inherit; font-size: var(--text-md); diff --git a/apps/desktop/test/permission-inline.test.mjs b/apps/desktop/test/permission-inline.test.mjs index b7c2877224..04ecd9e4cb 100644 --- a/apps/desktop/test/permission-inline.test.mjs +++ b/apps/desktop/test/permission-inline.test.mjs @@ -29,7 +29,7 @@ import { import { createNavigationIntentController } from "../src/lib/navigation-intent.ts"; const read = (path) => readFile(new URL(path, import.meta.url), "utf8"); -const [appSource, chatSurfaceSource, sessionPaneSource, composerSource, transcriptSource, cardSource, askCardSource, storeSource, browserSource, messageStyleSource] = +const [appSource, chatSurfaceSource, sessionPaneSource, composerSource, transcriptSource, cardSource, askCardSource, storeSource, browserSource, messageStyleSource, composerStyleSource] = await Promise.all([ readAppSource(), read("../src/components/ChatSurface.tsx"), @@ -41,6 +41,7 @@ const [appSource, chatSurfaceSource, sessionPaneSource, composerSource, transcri readStoreSource(), read("../src/components/workpanel/PluginViewTab.tsx"), read("../src/styles/messages.css"), + read("../src/styles/composer.css"), ]); const eventsSource = readStoreModuleSync("slices/events-slice.ts"); @@ -174,6 +175,22 @@ test("asktool card is a stepwise, non-expiring composer question surface", () => assert.match(messageStyleSource, /\.asktool-options[\s\S]*?overflow-y:\s*auto/); assert.match(messageStyleSource, /\.asktool-options[\s\S]*?max-height:\s*min\(320px,\s*36dvh\)/); assert.match(messageStyleSource, /\.asktool-options[\s\S]*?overscroll-behavior-y:\s*contain/); + // The card is a dock surface, not an in-flow tile: it paints the composer + // plate with its shadow and its rows are inlaid on that plate (issue #360). + assert.match( + composerStyleSource, + /\.composer-stack > \.asktool-card \{[^}]*background: var\(--ds-bg-composer\)/, + ); + assert.match( + composerStyleSource, + /\.composer-stack > \.asktool-card \{[^}]*box-shadow: var\(--ds-shadow-composer\)/, + ); + assert.doesNotMatch(messageStyleSource, /\.asktool-option \{[^}]*--ds-raised/); + assert.match(messageStyleSource, /\.asktool-option \{[^}]*background: var\(--ds-tile-deep\)/); + assert.match( + messageStyleSource, + /\.asktool-custom-input \{[^}]*background: var\(--ds-tile-deep\)/, + ); }); test("permission countdown uses its absolute receipt time", () => { diff --git a/docs/spec/04-ux/11-asktool-question-card.md b/docs/spec/04-ux/11-asktool-question-card.md index a6e85a1864..97a3d1d0e7 100644 --- a/docs/spec/04-ux/11-asktool-question-card.md +++ b/docs/spec/04-ux/11-asktool-question-card.md @@ -5,10 +5,13 @@ dialog. It is mounted in the same dock area as the Plan and Goal approval card, immediately above the composer input, so a paused question stays available at the active decision point instead of moving into transcript history. -It uses the existing message width, border, background, and button tokens so -that a paused question remains visually part of the conversation. The card -shell stays slim — 14 px × 16 px padding and a 2 px accent rail, matching the -permission card's compact footprint. The question text uses the compact card +It rides the composer plate — `--ds-bg-composer` with `--ds-shadow-composer`, +like the Plan and Goal approval bar — rather than the in-flow `--ds-tile` wash, +and its option rows and custom input are inlaid `--ds-tile-deep` fills on that +plate (D297, D435). The message width, typography, and button tokens still come +from the conversation so a paused question remains part of it. The card shell +stays slim — 14 px × 16 px padding, matching the permission card's compact +footprint, with no accent rail (D297). The question text uses the compact card body size (`--text-md`, 13 px) at medium weight — the same scale as the permission card's title and prompt in the same dock area — so it reads as the card's primary focal point without competing with the surrounding transcript. diff --git a/docs/spec/06-delivery/04-e2e-test-plan.md b/docs/spec/06-delivery/04-e2e-test-plan.md index 3db47281a6..ae6854d7a8 100644 --- a/docs/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/spec/06-delivery/04-e2e-test-plan.md @@ -4572,15 +4572,20 @@ identify the platform validation still needed. 6. In both light and dark palettes, inspect the settings rail, search, selected item, on-state knob, composer shell, plugin/capability searches, the code card's head band, the Mermaid canvas, tool output, the composer placeholder - and disabled send chip, and the dialog scrim and permission backdrop. Apply - custom surface variables, keyboard-focus both searches, then remove the - custom theme. + and disabled send chip, the dialog scrim and permission backdrop, and the + dock question card with its option row. Apply custom surface variables, + keyboard-focus both searches, then remove the custom theme. - **Expected**: - Work panel body reads as quiet `#fafafa` inset paper with a white header band. - Settings fields, browser URL, segment tracks, and shortcut keycaps use light inset fills; focused fields lift with a neutral ring. - Toggle on-state keeps a white knob on the near-black track. - Hover fills on file-tree/diff/resize ease with shared motion tokens, and the divider's 2px line is a 50% accent tint while hovered or dragged, so it never paints a solid white hairline across the dark plate; keyboard focus keeps the full accent. - Light dialog scrim is softer than the dark 45% veil (~28% ink). + - The dock question card paints the composer plate in both palettes — light + `#ffffff` with the composer shadow, dark 96% `#212121` — and its option rows + are inlaid `--ds-tile-deep` fills with no raised shadow. A custom + `--ds-bg-composer` / `--ds-tile-deep` repaints both, and removing it + restores the built-in paint. - Tool output keeps its cascade: light paints the same lighter tile over error output and over plain tool blocks, while dark shows the error tint and leaves plain blocks transparent. diff --git a/docs/spec/08-meta/decisions-log.md b/docs/spec/08-meta/decisions-log.md index 06521238cb..3be3868fc7 100644 --- a/docs/spec/08-meta/decisions-log.md +++ b/docs/spec/08-meta/decisions-log.md @@ -5629,3 +5629,33 @@ that was sitting at the bottom — including after the turn had finished. file, and an unavailable host contributes no exclusions. See ADR 0270, `04-ux/06-settings-ia.md` §2, `03-runtime/01-ipc-protocol.md` §12c, and E2E-SUBAGENT-settings-lists-builtin-defaults. + +## 2026-09-17 — The dock question card is a composer plate (#360, D435) + +- The asktool question card is mounted in the transparent composer dock + (`Composer.tsx` renders it as a direct `.composer-stack` child, and the + transcript mount is gone), yet it still painted the in-flow `--ds-tile` wash: + a 3.5% ink mix with no shadow. On the light page that leaves a `#f7f7f7` + panel with white option rows next to the composer plate below it, which is + how #360 item 1 ("选择交互面板缺少背景色") reads. The dock rule's own comment + already claimed the card "uses the same decision surface as Plan and Goal + approval", while `.plan-approval-bar` in that exact slot paints + `--ds-bg-composer` with `--ds-shadow-composer` (`04-ux/03-permission-ux.md` + §9, `04-ux/08-component-spec.md` §11.5). +- `.composer-stack > .asktool-card` now paints that plate: `--ds-bg-composer` + with `--ds-shadow-composer` (light `#ffffff`, dark + `color-mix(in oklab, #212121 96%, transparent)` with the composer shadow). +- Its controls move to the plate's inlaid tier, the layer `.plan-approval-split` + already uses on that same plate: `.asktool-option` and + `.asktool-custom-input` drop `--ds-raised` and `--ds-raised-shadow` for + `--ds-tile-deep`, and the hover/selected mix is rebased on `--ds-tile-deep`. + Without that flip the rows would be white on white in the light palette, + where `--ds-raised` and `--ds-bg-composer` are both `#ffffff`. The 15 px + option mark keeps `--ds-tile-deep` — the sidebar checkbox's mark — which + still reads against the inlaid row exactly as it does against the sidebar. +- Renderer only: no protocol, storage, host, permission, migration, or + preference change, and no new default. Both layers are existing tokens in + both palettes, so a contributed theme moves the plate and the rows with + `--ds-bg-composer` / `--ds-tile-deep`. See + `04-ux/11-asktool-question-card.md`, `04-ux/07-ui-design-system.md` §6.4, and + E2E-078. diff --git a/docs/zh-CN/spec/04-ux/11-asktool-question-card.md b/docs/zh-CN/spec/04-ux/11-asktool-question-card.md index 48f00a28c0..66670d99ea 100644 --- a/docs/zh-CN/spec/04-ux/11-asktool-question-card.md +++ b/docs/zh-CN/spec/04-ux/11-asktool-question-card.md @@ -8,10 +8,12 @@ Asktool 卡是内联输入框批准表面,而不是许可 紧邻输入框输入的上方,因此暂停的问题仍然可用 主动决策点,而不是进入成绩单历史。 -它使用现有的消息宽度、边框、背景和按钮标记,以便暂停的问题在 -视觉上仍然是对话的一部分。问题文本比聊天正文大半档 -(`--text-base-plus`,15 px),因此它读起来是卡片的主要焦点, -同时仍贴近周围成绩单的字号。 +它乘坐 composer 板 —— 与 Plan/Goal 批准条相同的 `--ds-bg-composer` 填充与 +`--ds-shadow-composer` 阴影 —— 而不是流动层的 `--ds-tile` 洗色;它的选项行与 +自定义输入是该板上的内嵌 `--ds-tile-deep` 填充(D297、D435)。消息宽度、字体 +与按钮令牌仍取自对话,暂停的问题因此仍是对话的一部分。问题文本使用紧凑卡体 +字号(`--text-md`,13 px)与中等字重,因此它读起来是卡片的主要焦点, +同时不与周围的成绩单争抢字号。 标题标识提示并显示进度。可点击的小指示器 编码已应答、未应答、跳过和当前状态,而不与 diff --git a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md index 43eecc5542..4554ecd370 100644 --- a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md @@ -3095,15 +3095,18 @@ IPC 请求无法关闭。 4. 将鼠标悬停在文件树行或 diff 标头上;聚焦浏览器 URL 字段。 5. 打开 confirmation/provider 对话框并检查稀松布。 6. 在明暗主题中分别检查设置导航轨、搜索、选中项、开启状态旋钮、输入框壳、插件/ - 能力搜索、代码卡标题带、Mermaid 画布、工具输出、输入占位符与禁用发送芯片, - 以及对话框遮罩和权限蒙层。应用自定义表面变量,使用键盘聚焦两类搜索,再移除 - 自定义主题。 + 能力搜索、代码卡标题带、Mermaid 画布、工具输出、输入占位符与禁用发送芯片、 + 对话框遮罩与权限蒙层,以及 dock 内的问题卡及其选项行。应用自定义表面变量, + 使用键盘聚焦两类搜索,再移除自定义主题。 - **预期**: - 工作面板主体读取为安静的 `#fafafa` 插页纸,带有白色标题带。 - 设置字段、浏览器 URL、分段轨道和快捷键键帽使用浅色嵌入填充;聚焦场通过中性环提升。 - 切换开启状态使白色旋钮保持在近乎黑色的轨道上。 - 悬停可通过共享运动令牌轻松填充 file-tree/diff/resize。 - 浅色对话稀松布比深色 45% 面纱(约 28% 墨水)更柔软。 + - dock 内的问题卡在两种主题里都绘制 composer 板 —— 浅色 `#ffffff` 配 composer + 阴影,深色 96% `#212121` —— 它的选项行是无抬升阴影的内嵌 `--ds-tile-deep` 填充。 + 自定义 `--ds-bg-composer` / `--ds-tile-deep` 会同时改变两者,移除后恢复内置配色。 - 工具输出保持既有级联:浅色在错误输出与纯文本工具块上都绘制同一层较浅底纹,深色 显示错误色调并让纯文本块保持透明。 - 自定义变量改变对应表面、键帽墨色及搜索焦点填充;移除后恢复内置 8-bit RGBA diff --git a/docs/zh-CN/spec/08-meta/decisions-log.md b/docs/zh-CN/spec/08-meta/decisions-log.md index 92cd58e47a..1e729245da 100644 --- a/docs/zh-CN/spec/08-meta/decisions-log.md +++ b/docs/zh-CN/spec/08-meta/decisions-log.md @@ -4271,3 +4271,10 @@ that amendment are retired by ADR 0268; the upstream work-panel lifecycle stays. - 设置 > 智能体 > 子智能体把五个随应用发布的默认项列为只读行,而用户自建的文档带启用开关,于是有一个委派对象完全无法关闭:复制一个内置项再停用副本,随应用发布的定义仍留在目录里,因为被停用的用户文档在加载器合并前就被过滤掉,也就没有遮蔽任何东西。 - 每个内置行现在都带同一个开关。句柄存放在 `/agent-capabilities/subagent-builtins.json` —— 一个独立文件,因为用户文档扫描会清理它永远看不到的 id 的状态 —— 并由 host-core 通过 `agents.disabledBuiltins` / `agents.setBuiltinEnabled` 暴露。Electron 主进程把被关闭的句柄交给 `loadSubagentDefinitions`,由它把它们从委派目录中剔除,而 `subagent/catalog` 仍会在其新增的 `builtins` 列表中返回该内置项,并标记 `enabled: false`。 - 被关闭的内置项保留自己的行并变暗,因此这个开关就是重新打开的入口。同名的用户文档继续生效并继续遮蔽随应用发布的定义,在文件夹中显示与删除仍然没有,因为内置不是文件;宿主不可用时也不会贡献任何排除项。见 ADR 0270、`04-ux/06-settings-ia.md` §2、`03-runtime/01-ipc-protocol.md` §12c 与 E2E-SUBAGENT-settings-lists-builtin-defaults。 + +## 2026-09-17 —— dock 内的问题卡是 composer 板(#360,D435) + +- asktool 问题卡挂在透明的 composer dock 里(`Composer.tsx` 把它渲染为 `.composer-stack` 的直接子节点,成绩单内的挂载已移除),但它仍在绘制流动层的 `--ds-tile` 洗色:3.5% 墨色混合、没有阴影。在浅色页面上这留下一块 `#f7f7f7` 面板加白色选项行,紧邻下方的 composer 板 —— 这正是 #360 第 1 项(“选择交互面板缺少背景色”)读到的样子。该 dock 规则自己的注释早已声称这张卡“使用与 Plan/Goal 批准相同的决策表面”,而同一槽位里的 `.plan-approval-bar` 绘制的是 `--ds-bg-composer` 加 `--ds-shadow-composer`(`04-ux/03-permission-ux.md` §9、`04-ux/08-component-spec.md` §11.5)。 +- `.composer-stack > .asktool-card` 现在绘制那块板:`--ds-bg-composer` 加 `--ds-shadow-composer`(浅色 `#ffffff`,深色 `color-mix(in oklab, #212121 96%, transparent)` 配合 composer 阴影)。 +- 它的控件移到该板的内嵌层 —— 也就是 `.plan-approval-split` 在同一块板上已经使用的层:`.asktool-option` 与 `.asktool-custom-input` 去掉 `--ds-raised` 与 `--ds-raised-shadow`,改用 `--ds-tile-deep`,悬停/选中混合也以 `--ds-tile-deep` 为基底。不做这次翻转,浅色主题下选项行会白上加白,因为该调色板里 `--ds-raised` 与 `--ds-bg-composer` 都是 `#ffffff`。15 px 的选项标记保留 `--ds-tile-deep` —— 与侧边栏复选框同一枚标记 —— 它与内嵌行之间的对比,和它与侧边栏之间的对比完全一致。 +- 仅渲染层:无协议、存储、宿主、权限、迁移或偏好改动,也没有新增默认值。两层在两种调色板里都是既有 token,因此贡献主题可以分别用 `--ds-bg-composer` / `--ds-tile-deep` 移动板与行。见 `04-ux/11-asktool-question-card.md`、`04-ux/07-ui-design-system.md` §6.4 与 E2E-078。 diff --git a/scripts/e2e-theme-surfaces.mjs b/scripts/e2e-theme-surfaces.mjs index 052413a5e7..f3893d6935 100644 --- a/scripts/e2e-theme-surfaces.mjs +++ b/scripts/e2e-theme-surfaces.mjs @@ -22,6 +22,7 @@ try { await writeFile(join(temp, "index.html"), `Theme surface regression${css.map((path) => ``).join("")}
Composer surface
Dialog scrim
Permission veil
+
Question

Answer prose with a K keycap and an inline chip.

thinking code
Code card head band
Mermaid canvas
Tool output
Plain tool output
Error tool output
`); diff --git a/scripts/e2e/theme-surfaces.js b/scripts/e2e/theme-surfaces.js index bacc0493fe..09ff1d5067 100644 --- a/scripts/e2e/theme-surfaces.js +++ b/scripts/e2e/theme-surfaces.js @@ -32,7 +32,23 @@ const surfaces = { thinkingCode: [".thinking-prose code", "--ds-thinking-code-bg"], sendDisabled: [".send-btn:disabled", "--ds-send-disabled-bg"], kbd: [".prose-chat kbd", "--ds-prose-kbd-fg", "ink"], + asktoolCard: [".composer-stack > .asktool-card", "--ds-bg-composer"], + asktoolOption: [".asktool-option", "--ds-tile-deep"], }; + +// Issue #360: the dock question card rides the composer plate and its option +// rows are inlaid on it. Sampled the same way as the table above. +Object.assign(DEFAULT_RGBA.light, { + asktoolCard: [255, 255, 255, 255], + asktoolOption: [26, 26, 26, 20], +}); +Object.assign(DEFAULT_RGBA.dark, { + asktoolCard: [33, 33, 33, 245], + asktoolOption: [255, 255, 255, 20], +}); +const COMPOSER_SHADOW = "rgba(0, 0, 0, 0.04) 0px 3px 7.5px 0px, rgba(0, 0, 0, 0.05) 0px 0px 20px 0px"; +Object.assign(DEFAULT_SHADOWS.light, { asktoolCard: COMPOSER_SHADOW }); +Object.assign(DEFAULT_SHADOWS.dark, { asktoolCard: COMPOSER_SHADOW }); const customColors = { "--ds-settings-rail-bg": "#243645", "--ds-settings-field-bg": "#365476", @@ -50,6 +66,7 @@ const customColors = { "--ds-thinking-code-bg": "#4a2f5e", "--ds-send-disabled-bg": "#6b5c2f", "--ds-prose-kbd-fg": "#a1b2c3", + "--ds-tile-deep": "#586166", }; const canvas = document.createElement("canvas"); canvas.width = canvas.height = 1;