fix(gui): the collapsed plan hint accounts for parallel active items

Lifting the single-in_progress cap makes a list shape reachable that the
web surfaces never received. Two sites derived their one-line summary with
todos.find(t => t.status === 'in_progress') — the collapsed TodoPanel header
and the todo_write row — which was total under the old cap and silently
dropped every active item but the first once several could match: a plan
with three running tasks collapsed to the name of one. The expanded list
was always correct, so neither PR's tests covered it.

Both sites now take planSummary in contract/todo-plan-model.ts, the
domain-shared face both the skeleton and toolviews domains may import; the
duplicated derivation was why one find could be fixed while the other
stayed wrong. The hint names the first active item and suffixes +<n> for
the rest, so the collapsed line reports how many tasks are running.

The web fixture's todo sample now runs two items in_progress, so the
assembled web transcript replays a parallel plan: the row reads
'1/4 已完成 · 实现 fixture 样本 +1' over the built bundles.
This commit is contained in:
Chinesezjc
2026-07-27 14:25:02 +08:00
parent b2a4341ceb
commit f8b0bd31d3
13 changed files with 167 additions and 38 deletions

View File

@@ -5,7 +5,10 @@
// surfaces: the dedicated TodoRow in the chat flow (keyed toolview, summary
// derived from the call args) and the TodoPanel plan strip riding the
// 'conversation.input.dock' slot (fed by ConversationSnapshot.todos, seeded
// by the tail history page), including the collapse interaction.
// by the tail history page), including the collapse interaction. The sample
// plan runs two items in_progress at once, so both surfaces are pinned against
// a parallel plan — the collapsed one-line hint must account for the second
// active item instead of naming the first and dropping it.
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { act, cleanup, fireEvent, screen, waitFor, within } from '@testing-library/react'
@@ -143,7 +146,7 @@ it('renders the todo_write turn: dedicated tool row + the dock plan strip', asyn
})),
}).toMatchInlineSnapshot(`
{
"panelHeader": "Plan1/3",
"panelHeader": "Plan1/4",
"panelItems": [
{
"status": "completed",
@@ -153,12 +156,16 @@ it('renders the todo_write turn: dedicated tool row + the dock plan strip', asyn
"status": "in_progress",
"text": "●实现 fixture 样本",
},
{
"status": "in_progress",
"text": "●跑后台构建",
},
{
"status": "pending",
"text": "○浏览器验收",
},
],
"row": "☰更新任务清单1/3 已完成 · 实现 fixture 样本",
"row": "☰更新任务清单1/4 已完成 · 实现 fixture 样本 +1",
"rowState": "ok",
}
`)
@@ -179,11 +186,11 @@ it('collapses the plan strip to the in-progress hint and restores it', async ()
listGone: panel.querySelector('ul') === null,
}).toMatchInlineSnapshot(`
{
"collapsedHeader": "Plan1/3实现 fixture 样本",
"collapsedHeader": "Plan1/4实现 fixture 样本 +1",
"listGone": true,
}
`)
fireEvent.click(header)
expect(panel.querySelectorAll('li')).toHaveLength(3)
expect(panel.querySelectorAll('li')).toHaveLength(4)
})