Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions apps/desktop/src/styles/composer.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions apps/desktop/src/styles/messages.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand All @@ -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;
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down
19 changes: 18 additions & 1 deletion apps/desktop/test/permission-inline.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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");
Expand Down Expand Up @@ -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", () => {
Expand Down
11 changes: 7 additions & 4 deletions docs/spec/04-ux/11-asktool-question-card.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 8 additions & 3 deletions docs/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions docs/spec/08-meta/decisions-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -5653,3 +5653,33 @@ that was sitting at the bottom — including after the turn had finished.
- See ADR 0272, `05-security/01-security.md` §4.1,
`03-runtime/09-logging-and-observability.md`, and
`06-delivery/04-e2e-test-plan.md` E2E-SKILL-MARKET-NET-BOUNDARY.

## 2026-09-17 — The dock question card is a composer plate (#360, D437)

- 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.
10 changes: 6 additions & 4 deletions docs/zh-CN/spec/04-ux/11-asktool-question-card.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)与中等字重,因此它读起来是卡片的主要焦点,
同时不与周围的成绩单争抢字号。

标题标识提示并显示进度。可点击的小指示器
编码已应答、未应答、跳过和当前状态,而不与
Expand Down
9 changes: 6 additions & 3 deletions docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions docs/zh-CN/spec/08-meta/decisions-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -4279,3 +4279,10 @@ that amendment are retired by ADR 0268; the upstream work-panel lifecycle stays.
- 拒绝与市场的 `failureDetails` 现在都带 `route`,因此「直连线路上的 fake-IP 拒绝」与「读不出线路的拒绝」在诊断里可以区分。
- MCP 市场仍使用自己的地址钉定 Node HTTPS 守卫(ADR 0245),本次不变;fake-IP 环境下它的源仍会被拒绝。
- 见 ADR 0272、`05-security/01-security.md` §4.1、`03-runtime/09-logging-and-observability.md` 与 `06-delivery/04-e2e-test-plan.md` 的 E2E-SKILL-MARKET-NET-BOUNDARY。

## 2026-09-17 —— dock 内的问题卡是 composer 板(#360,D437)

- 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。
1 change: 1 addition & 0 deletions scripts/e2e-theme-surfaces.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ try {
await writeFile(join(temp, "index.html"), `<!doctype html><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' data:"><title>Theme surface regression</title>${css.map((path) => `<link rel="stylesheet" href="${path}">`).join("")}<style>body { padding: 32px; display: flex; gap: 32px; align-items: flex-start; } .fixture-controls { width: 420px; display: grid; gap: 24px; } .fixture-prose { width: 420px; display: grid; gap: 12px; } .settings-shell-full { width: 275px; height: 340px; } .settings-toggle { margin: 20px; } /* The veils are fixed full-screen layers in the app; keep them in flow here so they do not cover the other fixtures. Only position changes, never the paint. */ .fixture-controls .overlay, .fixture-controls .plugins-modal-backdrop { position: static; inset: auto; padding: 12px; animation: none; }</style><body>
<section class="settings-shell-full"><nav class="settings-nav"><div class="settings-nav-top"><input class="settings-search" placeholder="Search settings"></div><div class="settings-nav-scroll"><button class="settings-nav-item active">General</button><button class="settings-nav-item">Appearance</button><button class="settings-toggle on" aria-label="Enabled"><span class="settings-toggle-thumb"></span></button></div></nav></section>
<section class="fixture-controls"><div class="composer-shell">Composer surface</div><input class="plugins-search" placeholder="Search plugins"><div class="agent-capability-search-wrap"><input class="agent-capability-search" placeholder="Search capabilities"></div><div class="overlay">Dialog scrim</div><div class="plugins-modal-backdrop">Permission veil</div></section>
<section class="fixture-controls"><div class="composer-stack"><section class="asktool-card"><div class="asktool-card-header"><span class="asktool-card-title">Question</span></div><button class="asktool-option">Blue</button><button class="asktool-option selected">Green</button><input class="asktool-custom-input" placeholder="Other" aria-label="Other"></section></div></section>
<section class="fixture-prose"><div class="prose-chat"><p>Answer prose with a <kbd>K</kbd> keycap and an <code>inline chip</code>.</p></div><div class="thinking-prose"><code>thinking code</code></div><div class="code-block-head">Code card head band</div><div class="mermaid-block-body">Mermaid canvas</div><button class="send-btn" disabled>Send</button></section>
<section class="fixture-tools"><div class="tool-row-content">Tool output</div><div class="tool-block is-plain"><div class="tool-row-content">Plain tool output</div></div><div class="tool-row-content is-error">Error tool output</div></section>
<script src="renderer.js"></script>`);
Expand Down
17 changes: 17 additions & 0 deletions scripts/e2e/theme-surfaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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;
Expand Down