From 6902b51feaf8f9df1e3da27fdd542b1dc24d1b59 Mon Sep 17 00:00:00 2001 From: creatixchu Date: Wed, 5 Aug 2026 16:55:20 +0800 Subject: [PATCH] fix(web): address review on the turn-tail actions gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct the Agent Note's consequence: a running turn withholds the footer below its own trigger bubble, while every earlier completed turn keeps its seat — which the package test already asserts. Give the running-phase barrier an explicit budget: it is armed before the park and awaited after the stop click, so the 30s replay default left no headroom for the marker poll, the UI polls, and two aria captures. Number the running-turn test's boundary seqs like the log does, with each turn/end strictly after its own nodes. --- ...tail-actions-require-a-completed-turn.i18n.yaml | 4 ++-- ...5-turn-tail-actions-require-a-completed-turn.md | 2 +- ...urn-tail-actions-require-a-completed-turn.zh.md | 2 +- apps/web/tests/turn-tail-actions.e2e.ts | 6 +++++- .../ui-conversation/tests/chat-view.spec.tsx | 14 ++++++++------ 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.i18n.yaml index b94c395c70..72d3ae50b8 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.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/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.md -2026-08-05-turn-tail-actions-require-a-completed-turn.md: b6d59c7d73daaea0233e51e5626ee8cbbec639dd -2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md: c89859d779c6c07c4576056bbe1c4e32250eb294 +2026-08-05-turn-tail-actions-require-a-completed-turn.md: 689d50bb86c830d6e428239f112568f00d74c9b8 +2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md: 2cc426bbb82acb8f57d491b0f068e89771699357 diff --git a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.md b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.md index b6d59c7d73..689d50bb86 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.md +++ b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.md @@ -28,4 +28,4 @@ This is the same completion fact the branch control and the run-time label alrea ## Consequences -During a running turn the conversation carries no message footer past the user bubble; the seat appears once when `turn/end` lands, which adds one 28px row under the settled answer at that moment. A turn whose `turn/end` is outside the loaded window grants nothing, which cannot arise from paging because a turn's end follows its own nodes. `apps/web/tests/turn-tail-actions.e2e.ts` pins both states through the assembled application: a `hang` sidecar on the second model call parks a turn whose first step narrated before calling bash, and the two goldens hold the parked flow and the flow after stopping. Package tests cover the derivation directly and the running-turn render. +A running turn carries no message footer below the user bubble that triggered it, while every earlier completed turn keeps its own; the seat appears once when `turn/end` lands, which adds one 28px row under the settled answer at that moment. A turn whose `turn/end` is outside the loaded window grants nothing, which cannot arise from paging because a turn's end follows its own nodes. `apps/web/tests/turn-tail-actions.e2e.ts` pins both states through the assembled application: a `hang` sidecar on the second model call parks a turn whose first step narrated before calling bash, and the two goldens hold the parked flow and the flow after stopping. Package tests cover the derivation directly and the running-turn render. diff --git a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md index c89859d779..2cc426bbb8 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md @@ -28,4 +28,4 @@ assistant IconActions 此前只从已定稿的 transcript(文本记录)推 ## 后果 -轮次运行期间,会话中除用户气泡外不再有任何消息操作栏;座位在 `turn/end` 到达时一次性出现,此刻已定稿答案下方会多出一行 28px。`turn/end` 落在加载窗口之外的轮次不授予座位,而翻页不会造成这种情况,因为一个轮次的结束事件排在它自己的节点之后。`apps/web/tests/turn-tail-actions.e2e.ts` 通过组装后的应用钉住两种状态:`hang` sidecar 作用在第二次模型调用上,把一个首步先叙述再调用 bash 的轮次挂住,两份 golden 分别记录挂起中的流程和停止之后的流程。包级测试直接覆盖该推导以及运行中轮次的渲染结果。 +运行中的轮次在触发它的用户气泡之下不再有任何消息操作栏,而此前每个已完成轮次仍保留各自的座位;座位在 `turn/end` 到达时一次性出现,此刻已定稿答案下方会多出一行 28px。`turn/end` 落在加载窗口之外的轮次不授予座位,而翻页不会造成这种情况,因为一个轮次的结束事件排在它自己的节点之后。`apps/web/tests/turn-tail-actions.e2e.ts` 通过组装后的应用钉住两种状态:`hang` sidecar 作用在第二次模型调用上,把一个首步先叙述再调用 bash 的轮次挂住,两份 golden 分别记录挂起中的流程和停止之后的流程。包级测试直接覆盖该推导以及运行中轮次的渲染结果。 diff --git a/apps/web/tests/turn-tail-actions.e2e.ts b/apps/web/tests/turn-tail-actions.e2e.ts index 19d14a7a0c..11e22d29d4 100644 --- a/apps/web/tests/turn-tail-actions.e2e.ts +++ b/apps/web/tests/turn-tail-actions.e2e.ts @@ -109,7 +109,11 @@ describe('web e2e: assistant IconActions wait for the turn to end', () => { return { patches: [{ at: 1, entry: { kind: 'hang', readyFile: marker } }] } }) onTestFailed(() => saveFailureShot(page, 'web-e2e-turn-tail-actions')) - const { settled } = await sendPrompt() + // The barrier is armed before the park and awaited only after the stop + // click, so its budget must cover the whole parked phase: marker poll, + // three UI polls, and two captures with their stability windows. The + // replay default (30s) leaves no headroom on a slow runner. + const { settled } = await sendPrompt(120_000) // The marker IS the synchronization: the second call is provably parked, // so the first step's message and tool result are already durable. await expect.poll(() => existsSync(marker), { timeout: 20_000 }).toBe(true) diff --git a/packages/client/ui-conversation/tests/chat-view.spec.tsx b/packages/client/ui-conversation/tests/chat-view.spec.tsx index 8702d9bcde..20daca0d7f 100644 --- a/packages/client/ui-conversation/tests/chat-view.spec.tsx +++ b/packages/client/ui-conversation/tests/chat-view.spec.tsx @@ -537,18 +537,20 @@ describe('ChatView', () => { nodes: [ user(1, 'first'), assistant(2, 'previous answer', 1), - user(3, 'second'), - assistant(4, 'mid-turn text', 2), + user(4, 'second'), + assistant(5, 'mid-turn text', 2), ], - turnEnds: new Map([[1, 2]]), + // Boundary seqs follow the log: a turn/end is strictly after its own nodes. + turnEnds: new Map([[1, 3]]), }) const view = render() - // 2 user + the settled turn-1 tail; turn 2's narration stays chrome-free - // while its tool runs, so the footer never appears and then moves. + // 2 user + the settled turn-1 tail, which keeps its seat while a later + // turn runs; turn 2's narration stays chrome-free while its tool runs, so + // the footer never appears and then moves. expect(view.getAllByRole('button', { name: '复制' })).toHaveLength(3) expect(view.getByText('mid-turn text')).toBeTruthy() // turn/end lands: the same node becomes the settled answer and takes the seat. - act(() => { h.set({ running: false, runningCalls: [], turnEnds: new Map([[1, 2], [2, 5]]) }) }) + act(() => { h.set({ running: false, runningCalls: [], turnEnds: new Map([[1, 3], [2, 6]]) }) }) expect(view.getAllByRole('button', { name: '复制' })).toHaveLength(4) })