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) })