fix(web-todo): dock-owned selection, keyboard-openable row, documented tail todos field

Three ds-review-bot round-8 findings on the todo display surfaces.

TodoPanel took the whole `useSession` hook and cast the snapshot to reach
`todos`, which put slot plumbing and an unchecked cast inside the presentation
component. The panel now takes `todos: readonly TodoItem[]`; TodoDock does the
selecting, matching the QueueDock posture the dock slot already establishes.

The todo row carried `onClick` with no keyboard route, so its details panel was
mouse-only. It now takes ToolRow's route verbatim: `role="button"`,
`tabIndex={0}`, and an Enter/Space handler that claims the event. The row stays
a `<div>` because a `<button>` flattens its inline spans.

`session.history`'s tail-only `todos` field was documented at the TS signature
but not in the apiproxy README pair, and the Agent Note claimed the feature
added no new wire vocabulary. Both README sides now state the tail/omission
semantics (an omitted field on a tail response is the empty plan, not unchanged
state), and the note records the one added field instead of denying it.

Tests: TodoPanel specs render the plain list; new TodoDock specs cover selection,
live follow, rollback-to-empty, and the registration shape; a new row spec pins
Enter/Space activation and non-activating keys.
This commit is contained in:
Chinesezjc
2026-07-27 12:08:46 +08:00
parent 8ebdad5076
commit d2bb2a809b
9 changed files with 92 additions and 40 deletions

View File

@@ -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-23-web-todo-display.md
2026-07-23-web-todo-display.md: a8ee5a5d2799b6e2f9ff442e0bd6230b46e9374b
2026-07-23-web-todo-display.zh.md: 0bf7157789a0be8f2c68d462bc815db8cf07d96e
2026-07-23-web-todo-display.md: 830f55c86c893c4942a1a9d3b8529395d5f5e38b
2026-07-23-web-todo-display.zh.md: e68928d7eddaaa92ac831722a738ee2002342b38

View File

@@ -33,4 +33,4 @@ The dedicated `todo_write` chat row is a plain registrant plugin (`todoToolview`
## Consequences
Replay correctness is owned by one code path: any future change to window rebuild keeps todos consistent for free, and the fixture (fx-alpha turn 65) plus the assembled keyless snapshot (`apps/web/tests/todo-display.snapshot.ts`) pin the full chain (row summary and state, dock panel content, collapse round-trip) over the built client graph. `todos` is a required `ConversationSnapshot` field, so scripted fakes in specs must carry it. The TUI panel is untouched (the automation-only ACP bridge deliberately omits todo presentation); the web surfaces render the same event with no new wire vocabulary. Cold-load reconstruction is host-backed: the tail history page carries `todos` — the full-log latest `todo/write`, computed independently of the page window (the same backscan posture the view pairing uses) — so a reopened session restores the plan even when the last write precedes the window; that value survives an older-page prepend, is overwritten by any later write, and resets to empty when a tail response carries no projection.
Replay correctness is owned by one code path: any future change to window rebuild keeps todos consistent for free, and the fixture (fx-alpha turn 65) plus the assembled keyless snapshot (`apps/web/tests/todo-display.snapshot.ts`) pin the full chain (row summary and state, dock panel content, collapse round-trip) over the built client graph. `todos` is a required `ConversationSnapshot` field, so scripted fakes in specs must carry it. The TUI panel is untouched (the automation-only ACP bridge deliberately omits todo presentation); the web surfaces render the same event, adding one wire field and no new event type. That field is how cold-load reconstruction stays host-backed: the tail history page carries `todos` — the full-log latest `todo/write`, computed independently of the page window (the same backscan posture the view pairing uses) — so a reopened session restores the plan even when the last write precedes the window; that value survives an older-page prepend, is overwritten by any later write, and resets to empty when a tail response carries no projection.

View File

@@ -33,4 +33,4 @@ Status: implemented
## Consequences
回放正确性由一条代码路径掌管:未来对窗口重建的任何改动都免费保持 todos 一致fx-alpha 第 65 轮的 fixture测试前置数据加 assembled keyless snapshot`apps/web/tests/todo-display.snapshot.ts`在构建产物客户端全图上钉住整条链行摘要与状态、dock 面板内容、折叠往返)。`todos``ConversationSnapshot` 的必填字段,所以 spec 里脚本化的 fake 必须带上它。TUI 面板未受改动(自动化专用的 ACP 桥接刻意不做 todo 呈现Web 各面渲染同一个事件,不引入任何新的协议词汇。冷加载重建由 host 兜底history 尾页附带 `todos`——全量 log 上最新一次 `todo/write` 的投影,独立于分页窗口计算(与 view 配对同一种 backscan 姿势)——因此重开会话时即使最后一次写入落在窗口之前,计划也照常恢复;该值跨往前翻页保留,之后的任何写入照常覆盖,而尾页响应不带投影时复位为空。
回放正确性由一条代码路径掌管:未来对窗口重建的任何改动都免费保持 todos 一致fx-alpha 第 65 轮的 fixture测试前置数据加 assembled keyless snapshot`apps/web/tests/todo-display.snapshot.ts`在构建产物客户端全图上钉住整条链行摘要与状态、dock 面板内容、折叠往返)。`todos``ConversationSnapshot` 的必填字段,所以 spec 里脚本化的 fake 必须带上它。TUI 面板未受改动(自动化专用的 ACP 桥接刻意不做 todo 呈现Web 各面渲染同一个事件,只新增一个协议字段,不新增事件类型。冷加载重建正是靠这个字段由 host 兜底history 尾页附带 `todos`——全量 log 上最新一次 `todo/write` 的投影,独立于分页窗口计算(与 view 配对同一种 backscan 姿势)——因此重开会话时即使最后一次写入落在窗口之前,计划也照常恢复;该值跨往前翻页保留,之后的任何写入照常覆盖,而尾页响应不带投影时复位为空。