From 515eca7dc772cb7d65ed3999620e8ae0cda72605 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 1 Sep 2026 20:21:11 +0800 Subject: [PATCH 01/32] fix(web): keep turn previews above code banners --- apps/web/tests/chat-scroll-contract.e2e.ts | 12 ++++++++++++ packages/client/ui-chat/README.i18n.yaml | 4 ++-- packages/client/ui-chat/README.md | 2 +- packages/client/ui-chat/README.zh.md | 2 +- .../ui-chat/src/client/chat/TurnNavigator.module.css | 4 +++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/web/tests/chat-scroll-contract.e2e.ts b/apps/web/tests/chat-scroll-contract.e2e.ts index 37996ef301..49e14028ae 100644 --- a/apps/web/tests/chat-scroll-contract.e2e.ts +++ b/apps/web/tests/chat-scroll-contract.e2e.ts @@ -591,6 +591,18 @@ describe('web e2e: long Chat scroll contract', () => { await expect.poll(() => tooltip.count(), { timeout: 15_000 }).toBe(1) expect(await tooltip.textContent()).toContain(HISTORY_FIXTURE.markers.user(1)) expect(await tooltip.textContent()).toContain(HISTORY_FIXTURE.markers.assistant(1)) + const transcriptLayers = await tooltip.evaluate((preview) => { + const railSlot = preview.closest('nav')?.parentElement + const codeBanner = document.querySelector('.md-code-block > :first-child') + if (!(railSlot instanceof HTMLElement) || codeBanner === null) { + throw new Error('turn preview or code-block banner stacking context is unavailable') + } + return { + codeBanner: Number(getComputedStyle(codeBanner).zIndex), + rail: Number(getComputedStyle(railSlot).zIndex), + } + }) + expect(transcriptLayers.rail).toBeGreaterThan(transcriptLayers.codeBanner) await world.page.keyboard.press('Enter') // The jump pages history in and lands on turn 1: its mark flips to the diff --git a/packages/client/ui-chat/README.i18n.yaml b/packages/client/ui-chat/README.i18n.yaml index 7df0e3ba3e..8183f95981 100644 --- a/packages/client/ui-chat/README.i18n.yaml +++ b/packages/client/ui-chat/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-chat/README.md -README.md: 19cfcf4d14f7e805d6113eeb4e34a586f7150574 -README.zh.md: 18e20eeea15cf3eeb5e8e4a777fa534251232a81 +README.md: 79214bf2feb8384cecbb152aa31af162dc6dfdde +README.zh.md: 674959e1974c74abba75f179eff50fe8488458fc diff --git a/packages/client/ui-chat/README.md b/packages/client/ui-chat/README.md index 19cfcf4d14..79214bf2fe 100644 --- a/packages/client/ui-chat/README.md +++ b/packages/client/ui-chat/README.md @@ -46,7 +46,7 @@ Settings → General exposes a persisted `Normal` / `Compact` conversation-displ ## Scroll ownership -Chat restores semantic anchors across history prepend and renderer remounts. While the reader is pinned to the floor, `ResizeObserver` follows the new floor and selects the latest loaded Turn without reading row geometry. Once the reader moves away, flow-height changes preserve the top position and the reading-line geometry selects the active Turn ([loaded-Turn navigation](../../../.agents/notes/implemented/feature/2026-08-25-loaded-turn-chat-navigation.md)). +Chat restores semantic anchors across history prepend and renderer remounts. While the reader is pinned to the floor, `ResizeObserver` follows the new floor and selects the latest loaded Turn without reading row geometry. Once the reader moves away, flow-height changes preserve the top position and the reading-line geometry selects the active Turn. Turn-rail previews paint above sticky Markdown code-block banners, while the rail frame remains inside the transcript band above the composer ([loaded-Turn navigation](../../../.agents/notes/implemented/feature/2026-08-25-loaded-turn-chat-navigation.md)). ----- diff --git a/packages/client/ui-chat/README.zh.md b/packages/client/ui-chat/README.zh.md index 18e20eeea1..674959e197 100644 --- a/packages/client/ui-chat/README.zh.md +++ b/packages/client/ui-chat/README.zh.md @@ -46,7 +46,7 @@ Chat 会为每个非空的初始或恢复请求、显式消息序列起点或真 ## 滚动归属 -Chat 会在历史前插与 renderer 重新挂载时恢复语义锚点。读者跟随底部时,`ResizeObserver` 追随新的底部,并且无需读取行几何就选中最后一个已加载 Turn;读者离开底部后,高度变化会保持顶部位置,再由阅读线几何选择活跃 Turn([已加载 Turn 导航](../../../.agents/notes/implemented/feature/2026-08-25-loaded-turn-chat-navigation.zh.md))。 +Chat 会在历史前插与 renderer 重新挂载时恢复语义锚点。读者跟随底部时,`ResizeObserver` 追随新的底部,并且无需读取行几何就选中最后一个已加载 Turn;读者离开底部后,高度变化会保持顶部位置,再由阅读线几何选择活跃 Turn。轮次导航预览位于 Markdown 代码块粘性头栏上方,而导航外框始终处于 composer 上方的 transcript 区域内([已加载 Turn 导航](../../../.agents/notes/implemented/feature/2026-08-25-loaded-turn-chat-navigation.zh.md))。 ----- diff --git a/packages/client/ui-chat/src/client/chat/TurnNavigator.module.css b/packages/client/ui-chat/src/client/chat/TurnNavigator.module.css index 5e771e6727..b212032c96 100644 --- a/packages/client/ui-chat/src/client/chat/TurnNavigator.module.css +++ b/packages/client/ui-chat/src/client/chat/TurnNavigator.module.css @@ -3,7 +3,9 @@ .slot { position: sticky; top: 0; - z-index: 6; + /* Above transcript CodeBlock banners (z-index 6). The rail band excludes + the composer, so its preview never needs to cross the footer. */ + z-index: 7; height: 0; pointer-events: none; } From c11c3f98ad8d26bdbe9ae7c543e14cfa4cb1d412 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Tue, 1 Sep 2026 01:50:27 +0800 Subject: [PATCH 02/32] docs(ui-deliverables): record CSS overflow policy --- ...6-07-31-web-workspace-file-links.i18n.yaml | 4 +-- .../2026-07-31-web-workspace-file-links.md | 6 ++--- .../2026-07-31-web-workspace-file-links.zh.md | 6 ++--- ...6-09-01-css-produced-file-layout.i18n.yaml | 6 +++++ .../2026-09-01-css-produced-file-layout.md | 27 +++++++++++++++++++ .../2026-09-01-css-produced-file-layout.zh.md | 27 +++++++++++++++++++ .../client/ui-deliverables/README.i18n.yaml | 4 +-- packages/client/ui-deliverables/README.md | 4 +-- packages/client/ui-deliverables/README.zh.md | 4 +-- 9 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 .agents/notes/implemented/simplification/2026-09-01-css-produced-file-layout.i18n.yaml create mode 100644 .agents/notes/implemented/simplification/2026-09-01-css-produced-file-layout.md create mode 100644 .agents/notes/implemented/simplification/2026-09-01-css-produced-file-layout.zh.md diff --git a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.i18n.yaml index 5a5c329383..e8023fd069 100644 --- a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.md -2026-07-31-web-workspace-file-links.md: ddc093e1ce5e646f6b96f1517b2a26c9e10fe423 -2026-07-31-web-workspace-file-links.zh.md: f463de969bab7a47145abbbd46363b8807bcc673 +2026-07-31-web-workspace-file-links.md: 944d7bef9b83eb34895c5f74e511600a713d786b +2026-07-31-web-workspace-file-links.zh.md: 71f0051204e0cb8b65995fc88a9edb230db8f5be diff --git a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.md b/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.md index ddc093e1ce..944d7bef9b 100644 --- a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.md +++ b/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.md @@ -14,7 +14,7 @@ Two distinct defects sat behind that. The transcript never said what a turn had ## Decision -**A finished turn ends with the files it produced.** The row is its own plugin, `@deepseek-ai/dsh-client-ui-deliverables`, registered into the `conversation.chat.turnTail` hole the chat view renders between a closing message's body and its IconActions — ui-conversation owns the hole and the owner currency (nodes, closing seq, `openFile`), the plugin owns every policy. `producedForClosing` reads the paths off the mutation tools' own follow-along `locations` — a diff card, or a generic card whose `kind` is `edit` (the shape `str_replace_editor`'s insert presents) — so a turn's output is listed whether or not the closing message named it, and a new mutation tool joins by declaring what it does rather than by being added to a list. Reads, deletes, and failed calls contribute nothing; a path appears once per turn in first-seen order; accumulation resets on the turn boundary, so a turn that mutates and then ends without content text cannot spill into the next turn's row. The single-line lane measures its chips and localized remainder, then shows the largest prefix that fits (up to six) plus `+ N files`. One cordis.yml line composes the surface in or out; the unregistered hole renders nothing. +**A finished turn ends with the files it produced.** The row is its own plugin, `@deepseek-ai/dsh-client-ui-deliverables`, registered into the `conversation.chat.turnTail` hole the chat view renders between a closing message's body and its IconActions — ui-conversation owns the hole and the owner currency (nodes, closing seq, `openFile`), the plugin owns every policy. `producedForClosing` reads the paths off the mutation tools' own follow-along `locations` — a diff card, or a generic card whose `kind` is `edit` (the shape `str_replace_editor`'s insert presents) — so a turn's output is listed whether or not the closing message named it, and a new mutation tool joins by declaring what it does rather than by being added to a list. Reads, deletes, and failed calls contribute nothing; a path appears once per turn in first-seen order; accumulation resets on the turn boundary, so a turn that mutates and then ends without content text cannot spill into the next turn's row. CSS container-width bands select a prefix of up to six paths and its matching `+ N files` label, while flexbox shrinks and ellipsizes the visible basenames; the component performs no JavaScript layout observation ([decision](../simplification/2026-09-01-css-produced-file-layout.md)). One cordis.yml line composes the surface in or out; the unregistered hole renders nothing. **The path link reads as a link.** Underlined at rest, not only on hover. This is the smaller half of the diff and the larger half of the fix. @@ -28,9 +28,9 @@ Two distinct defects sat behind that. The transcript never said what a turn had - **Same-origin HTTP serving without isolation** — measurably unsafe, and recorded so nobody retries it: a document served beside `/api` drove `settings.describe` to a `200` with full data and `session.list` to 35 KB of every session's transcript, from a page that need not be agent-authored at all (a read row makes every file in a cloned repository openable). - **`Content-Security-Policy: sandbox` over that same-origin serving** — closes the hole by taking the document's origin away, which measurably breaks the pages this feature exists to show: the reported artifact throws `SecurityError` on load, and because an uncaught exception aborts the rest of its `