From 515eca7dc772cb7d65ed3999620e8ae0cda72605 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 1 Sep 2026 20:21:11 +0800 Subject: [PATCH] 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; }