feat(web-shell): collapsible TodoWrite history with status diff#5109
Conversation
Inline todo_write updates rendered as a generic, non-collapsible tool row crammed in with surrounding tool calls — the todo-specific renderer was effectively dead code because the detector matched the literal "todowrite" while the wire name is "todo_write" (kind "think"). - Detect the todo tool by name (todo_write / todowrite) instead of relying on the unrelated tool kind, reviving the rich rendering and also populating the floating todo panel that was empty on the daemon path. - Render each update as its own standalone group, collapsed by default to the per-snapshot diff (just-completed / just-started items) or the current step, expanding to the full checklist; the header shows completed/total. - Use consistent status glyphs (●/◐/○) in both collapsed and expanded views via a shared TodoView component used by ToolGroup and PlanMessage.
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Hey @wenshao — thanks for the PR, the collapsible todo history looks like a solid improvement. 👍
However, the PR body doesn't follow our pull request template. Could you update it to use the required sections? Specifically:
- What this PR does / Why it's needed — your "Summary" and "What changed" cover the same ground, just need the right headings.
- Reviewer Test Plan — needs the structured subsections: "How to verify", "Evidence (Before & After)", and the "Tested on" OS table. Your current "Test plan" section has the spirit of this but is missing the OS table and the Before/After evidence format.
- Risk & Scope — tradeoffs, what's out of scope, breaking changes.
- Linked Issues —
Closes #Nor references. <details>中文说明 — full Chinese translation of the English body.
This is a housekeeping ask — once the template is filled out, the actual review can proceed.
中文说明
@wenshao 你好,感谢 PR!可折叠的 todo 历史看起来是个不错的改进。
不过 PR 正文没有按照 PR 模板 来写。能否按照模板更新一下?主要是:
- What this PR does / Why it's needed — 现有的 "Summary" 和 "What changed" 内容够了,换一下标题即可。
- Reviewer Test Plan — 需要结构化的子节:如何验证步骤、前后对比证据、以及 Tested on 的操作系统表格。
- Risk & Scope — 权衡取舍、不在范围内的内容、破坏性变更。
- Linked Issues — 关联 issue。
<details>中文说明 — 英文正文的完整中文翻译。
纯粹是格式要求,更新后就可以继续 review 了。
— Qwen Code · qwen3.7-max
wenshao
left a comment
There was a problem hiding this comment.
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
|
Thanks for the triage check — updated the PR description to follow the template: What this PR does / Why it's needed, a structured Reviewer Test Plan (How to verify · Before/After evidence · Tested-on OS table), Risk & Scope, Linked Issues, and the full @qwen-code /triage 中文感谢 triage 检查——已按模板更新 PR 描述:补齐 What this PR does / Why it's needed、结构化的 Reviewer Test Plan(如何验证 · 前后对比证据 · Tested-on 系统表格)、Risk & Scope、Linked Issues,以及完整的 |
- [Critical] computeTodoTimeline keyed its running state on todo.id alone, but ids aren't globally unique (ACP assigns positional ids, models renumber per plan), so a later plan diffed against a previous plan's stale terminal status and silently dropped events in the collapsed view. Key on id+content so distinct tasks stay separate; add an id-reuse regression test. - Stabilize the TodoTimelineContext value with a signature-cached Map so streaming ticks that don't touch a todo snapshot no longer re-render every todo/plan row. - Drop the unused completed/total from TodoSnapshotDiff (consumers compute the count locally — single source of truth). - Fall back to the raw result summary when a todo_write payload is unparseable. - Add PlanMessage rendering tests and extractTodosFromToolCall coverage; remove stale "step time" comments left after dropping per-step timing.
|
Addressed the
All 286 web-shell tests pass; eslint + typecheck clean; app + lib build green. Resolving the threads below. 中文在
web-shell 286 测试全过;eslint + 类型检查干净;app + lib 构建通过。正在 resolve 下面的线程。 |
Follow-up to review on the id+content keying in computeTodoTimeline: - Document the two rare trade-offs in the todoStateKey doc (a mid-task reword on a stable id, and unrelated plans reusing both id and content), both degrading to "the collapsed diff omits one event" while the expanded list stays correct. - Pin behavior with tests: an item carried over and completed in a later turn (which id+content handles but a user-turn reset would drop), plus the two documented gaps. - Add todoTimelineSignature tests: stable across non-todo edits; changes on any id/status/content change.
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
wenshao
left a comment
There was a problem hiding this comment.
Round 2 incremental review (new SHA bada65bf5 since R1 8c580d586be). 14 files, +1032/-294. tsc 0 actionable (all pre-existing project-wide TS2835/TS4111), eslint 0, 286 tests pass. Reverse audit: no additional gaps.
3 high-confidence Suggestions + 3 low-confidence Suggestions posted inline. 2 low-confidence findings held for terminal only (cross-layer import adapters→utils, non-null assertions on todoItems).
— qwen3.7-max via Qwen Code /review
Local verification incl. real-browser render (merge reference)Verified Verdict: works end-to-end in a real browser, the review findings (incl. the [Critical] id-collision) are addressed at this head, and the diff-keying gaps are bounded and explicitly tested. Good to merge. 1. Real-browser render (headless Chromium via Playwright)Mounted the real components with a 3-item Collapsed (default): → N/M header Expanded (after a real click on the header): → 2. Test suite — green
3. Mutation — the two key fixes are non-vacuous
So the detection-by-name fix and the id+content keying (the [Critical] review finding) are each genuinely guarded. 4. Review findings + known limits
Conclusion: end-to-end correct in a real browser, fully tested (incl. the regression that the [Critical] fix prevents), with bounded, documented edge cases. Good to merge. 中文版(Chinese version)本地验证(含真实浏览器渲染,合并参考)在 Linux(Node 22.22.2)上验证 结论:在真实浏览器中端到端可用;review 的意见(含 [Critical] id 冲突)在此 head 已处理;diff keying 的缺口是有界的且有专门测试。可以合并。 1. 真实浏览器渲染(Playwright + headless Chromium)用一个 3 条目的 折叠(默认): → N/M 表头 展开(真实点击表头后): → 2. 测试套件 —— 全绿
3. 变异测试 —— 两处关键修复非空壳
因此"按名识别"修复与 id+内容 keying([Critical] review 意见)都被真正守护。 4. Review 意见 + 已知限制
结论:真实浏览器中端到端正确,测试充分(含 [Critical] 修复所阻止的回归),边界情况有界且有文档。可以合并。 |
|
@qwen-code /triage |
|
Thanks for the PR @wenshao! Template looks good ✓ — all required sections present, bilingual, with Before/After evidence and OS table. On direction: This fixes a real bug — the web-shell daemon path sends On approach: The scope feels right. The bug fix ( One thing worth noting: the Moving on to code review. 🔍 中文说明感谢 PR @wenshao! 模板完整 ✓ — 所有必需章节齐全,双语,有前后对比证据和系统表格。 方向: 这修了一个真实的 bug — web-shell daemon 路径发送 方案: 范围合理。bug 修复( 值得注意的一点: 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
…rage Address follow-up review: - Extract PlanEventSummary as the sole TodoTimelineContext consumer, mirroring ToolGroup's TodoToolBody so the memo-shielded PlanMessage stays stable when the timeline Map reference changes. - Note the spurious-`started` axis of the reword trade-off in the todoStateKey doc (a reword can drop a completion or emit a stray start). - Add direct isTodoWriteToolName tests (incl. the `todowrite` ACP variant) and a todoTimelineSignature empty-transcript test.
2a. Code ReviewIndependent proposal (before reading the diff): Comparison with the PR: Findings: No critical blockers. The code is well-structured and the previous review round's findings (id collision, context re-render, duplicate A few observations (non-blocking):
2b. TestingUnit tests (applied PR diff to worktree, ran locally): Lint: Clean on all 6 changed source files. CI (GitHub Actions): macOS ✅, Ubuntu ✅, Lint ✅, CodeQL ✅, Windows pending. Real-browser testing: tmux-based testing doesn't apply here — the web-shell is a browser-based UI rendered by Vite, not a terminal TUI. The 中文说明2a. 代码审查独立方案(读 diff 前): 与 PR 对比: 发现: 无关键阻塞项。代码结构良好,上一轮 review 的意见(id 冲突、context 重渲染、重复的 几个观察(非阻塞):
2b. 测试单元测试(在 worktree 中应用 PR diff 后本地运行): Lint: 6 个变更源文件全部干净。 CI: macOS ✅、Ubuntu ✅、Lint ✅、CodeQL ✅、Windows 待完成。 真实浏览器测试: tmux 测试不适用于此 — web-shell 是 Vite 渲染的浏览器 UI,不是终端 TUI。作者自己的 Playwright/headless-Chromium 验证覆盖了真实浏览器渲染:折叠态显示 — Qwen Code · qwen3.7-max |
|
Stepping back: this PR fixes a genuine regression in the web-shell daemon path (todo rendering broken due to name mismatch) and pairs it with a well-designed collapsible UI. The implementation is straightforward — a shared The code reads like someone who thought about the problem, not someone who threw abstractions at it. The id+content keying for My independent proposal matched the PR's approach, and the PR went further in the right ways (id+content keying, mutation-tested key fixes). 292 tests pass, lint is clean, CI green on macOS + Ubuntu. The author's own Playwright verification covers real-browser rendering with mutation testing to prove the fixes aren't vacuous. This is ready to ship. ✅ 中文说明退一步看:这个 PR 修复了 web-shell daemon 路径的真实回归(因名称不匹配导致 todo 渲染失效),并配上了设计良好的可折叠 UI。实现简洁 — 共享的 代码读起来像是思考过问题的人写的,而不是拿抽象堆砌的人。 我的独立方案与 PR 一致,PR 还在正确的方向上更进一步(id+content keying、变异测试验证关键修复)。292 测试通过,lint 干净,CI 在 macOS + Ubuntu 上绿。作者自己的 Playwright 验证覆盖了真实浏览器渲染并用变异测试证明修复非空壳。 可以合并了。✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
Qwen Code review did not complete successfully: Qwen review aborted with an API error before posting comments. See workflow logs. |
chiga0
left a comment
There was a problem hiding this comment.
Review Overview — PR #5109
PR: feat(web-shell): collapsible TodoWrite history with status diff
Author: wenshao | HEAD: 8e1f4bf (4 commits) | +1138/-292, 14 files
Base: main
Summary
Well-scoped UI enhancement giving todo_write updates a dedicated, collapsible rendering in the web-shell transcript. Core changes:
- Todo detection fix:
isTodoWriteToolName()matches bothtodo_write(wire name) andtodowrite(legacy/ACP), fixing the root cause where daemon-path todos were never matched. - Standalone grouping:
todo_writeand sub-agent tool calls each get their owntool_group, preventing them from merging with adjacent tool calls. - Shared
TodoViewcomponent:TodoEventSummary(collapsed diff) +TodoFullList(expanded checklist) reused by bothPlanMessage(ACP path) andToolLine(daemon path). - Timeline computation:
computeTodoTimeline()walks all messages and derives per-snapshot status transitions (started/completed), withtodoStateKey([id, content])preventing cross-plan id collision. - Referential stability:
todoTimelineSignature()+useRefmemoization keeps theTodoTimelineContextMap stable across unrelated streaming ticks. - Context isolation:
PlanEventSummaryandTodoToolBodyare the onlyuseContext(TodoTimelineContext)consumers, preventing memo-shielded parents from re-rendering.
Independent Findings: 0 blocking
The code is well-structured with clear data flow, proper memoization, documented trade-offs (reword edge case, recurring-plan collision), and comprehensive test coverage (~30+ new test cases across 4 test files). i18n keys verified present in both EN and CN locales.
Cross-Validation
| # | wenshao (self-review) | Severity | My Assessment |
|---|---|---|---|
| W1 | lastStatus keyed by todo.id — ids not globally unique |
Critical | FIXED in commit a1dee77a1e — composite key [id, content] via todoStateKey(). Trade-offs well-documented. |
| W2 | todoTimeline Map referential instability |
Suggestion | FIXED in commit 8e1f4bf — todoTimelineSignature + useRef memoization. |
| W3 | Stale JSDoc referencing "step time" | Suggestion | FIXED at HEAD — no stale references found. |
| W4 | PlanMessage has no rendering test |
Suggestion | FIXED — PlanMessage.test.tsx added (6 tests: collapse, expand, timeline diff, all-done, fallback, empty). |
| W5 | TodoToolBody blank when parse fails |
Suggestion | FIXED — fallback to result summary when !hasTodoList. |
| W6 | getFloatingTodos + computeTodoTimeline double iteration |
Suggestion | NOTED — both iterate messages but serve different consumers (floating panel vs timeline context). Merging adds complexity for minimal gain. |
| W7-W8 | extractTodosFromToolCall missing test coverage for rawOutput paths + isTodoWriteToolName |
Suggestion | FIXED — extractTodosFromToolCall tests cover all 3 paths + non-todo rejection. isTodoWriteToolName tests cover 4 matches + 4 rejections. |
| W9 | PlanMessage useContext bypasses memo |
Suggestion | FIXED — extracted PlanEventSummary as isolated context consumer. |
| W10-W11 | Blank row edge case + TodoFullList unbounded |
Low confidence | ACCEPTABLE — row header always renders; lists are model-bounded. |
Additional Audit Coverage
- Tool detection fix verified: Old
toolName !== 'todowrite' && kind !== 'other'returnedundefinedfortodo_write(underscore) withkind: 'think'— the exact daemon wire format. NewisTodoWriteToolName()correctly handles both names. - Standalone grouping logic:
isStandalonepredicate combiningisSubAgentToolCall+isTodoWriteToolNameis correct — bidirectional check prevents both directions of merging. - Status transition logic:
computeTodoTimelinecorrectly suppresses events for first-seen-completed items (prev === undefined), handlespending → completedtransitions (emits completed event), and produces empty events for unchanged re-emissions. - Context default safety:
TodoTimelineContextdefaults tonew Map(), so components outside the provider gracefully fall back to current-step display. - CSS migration: Old todo-specific classes removed from
ToolChrome.module.cssandPlanMessage.module.css, replaced by sharedTodoView.module.css— no orphaned styles.
Final Verdict — APPROVE
Clean, well-tested UI enhancement. The todo detection bug fix (todo_write vs todowrite) resolves a real regression in the daemon path. All self-review findings addressed across 4 commits. 0 independent blocking issues.
This review was generated by QoderWork AI
* feat(web-shell): collapsible TodoWrite history with status diff Inline todo_write updates rendered as a generic, non-collapsible tool row crammed in with surrounding tool calls — the todo-specific renderer was effectively dead code because the detector matched the literal "todowrite" while the wire name is "todo_write" (kind "think"). - Detect the todo tool by name (todo_write / todowrite) instead of relying on the unrelated tool kind, reviving the rich rendering and also populating the floating todo panel that was empty on the daemon path. - Render each update as its own standalone group, collapsed by default to the per-snapshot diff (just-completed / just-started items) or the current step, expanding to the full checklist; the header shows completed/total. - Use consistent status glyphs (●/◐/○) in both collapsed and expanded views via a shared TodoView component used by ToolGroup and PlanMessage. * fix(web-shell): scope todo diff per task identity; address review - [Critical] computeTodoTimeline keyed its running state on todo.id alone, but ids aren't globally unique (ACP assigns positional ids, models renumber per plan), so a later plan diffed against a previous plan's stale terminal status and silently dropped events in the collapsed view. Key on id+content so distinct tasks stay separate; add an id-reuse regression test. - Stabilize the TodoTimelineContext value with a signature-cached Map so streaming ticks that don't touch a todo snapshot no longer re-render every todo/plan row. - Drop the unused completed/total from TodoSnapshotDiff (consumers compute the count locally — single source of truth). - Fall back to the raw result summary when a todo_write payload is unparseable. - Add PlanMessage rendering tests and extractTodosFromToolCall coverage; remove stale "step time" comments left after dropping per-step timing. * test(web-shell): document todo-diff keying limits; cover signature Follow-up to review on the id+content keying in computeTodoTimeline: - Document the two rare trade-offs in the todoStateKey doc (a mid-task reword on a stable id, and unrelated plans reusing both id and content), both degrading to "the collapsed diff omits one event" while the expanded list stays correct. - Pin behavior with tests: an item carried over and completed in a later turn (which id+content handles but a user-turn reset would drop), plus the two documented gaps. - Add todoTimelineSignature tests: stable across non-todo edits; changes on any id/status/content change. * refactor(web-shell): isolate plan context read; expand todo test coverage Address follow-up review: - Extract PlanEventSummary as the sole TodoTimelineContext consumer, mirroring ToolGroup's TodoToolBody so the memo-shielded PlanMessage stays stable when the timeline Map reference changes. - Note the spurious-`started` axis of the reword trade-off in the todoStateKey doc (a reword can drop a completion or emit a stray start). - Add direct isTodoWriteToolName tests (incl. the `todowrite` ACP variant) and a todoTimelineSignature empty-transcript test.
What this PR does
Gives
todo_writeupdates a dedicated, collapsible rendering in the web-shell transcript. Each update is now its own standalone block, collapsed by default to the current change — the items that just completed (●) or just started (◐), or the current step — with acompleted/totalcount in the header, and expands on click to the full checklist. Todo detection now matches the tool by name (todo_write/todowrite) instead of an exacttodowritestring, and status glyphs (● done · ◐ in progress · ○ pending) are consistent between the collapsed and expanded views via a sharedTodoViewcomponent reused by both the tool renderer and the ACP plan renderer.Why it's needed
In the web-shell daemon path, todo updates arrive as
todo_writetool calls withkind: "think", but the inline renderer keyed on the literal stringtodowrite, so the todo-specific UI never matched. Todos therefore rendered as a generic, non-collapsible tool row crammed into the same group box as the surrounding tool calls — hard to scan and impossible to fold. Fixing detection by name revives the rich rendering and, as a side effect, populates the floating todo panel that was previously always empty on the daemon path.Reviewer Test Plan
How to verify
Run the web-shell against a session that uses the todo tool:
npm run dev:daemon, open the browser, then resume or start a session where the model callstodo_write. Expected: each todo update appears as its ownTodoWrite N/Mblock, collapsed to the latest change / current step and separated from other tool calls; clicking it expands the full numbered checklist; the collapsed and expanded views use the same ● / ◐ / ○ glyphs. Automated:npm testinpackages/web-shell(273 passing), including a new component test that renders a realtodo_writetool (wire name +thinkkind) and a new transcript test asserting eachtodo_writeforms its own group instead of merging.Evidence (Before & After)
Before — a flat, non-collapsible row merged in with other tools:
After — a standalone block, collapsed by default to the current change:
After — expanded on click to the full checklist:
Tested on
Environment (optional)
Local
vitestunit/component tests + production build (npm run build, app + lib) on macOS; browser behavior covered by component tests that render the real tool, plusnpm run dev:daemonfor manual confirmation.Risk & Scope
todo_writetool calls render and group in the web-shell transcript (each update is now standalone rather than merged). Covered by unit + component + transcript tests; no data or API changes.Linked Issues
N/A — no associated issue.
中文说明
这个 PR 做了什么
为 web-shell 会话记录里的
todo_write更新提供专门的、可折叠的渲染。每次更新现在是一个独立的块,默认折叠为本次变化——刚完成(●)或刚开始(◐)的条目,或当前步骤——表头显示已完成/总数,点击展开为完整清单。todo 的识别现在按名字匹配(todo_write/todowrite),不再依赖精确字符串todowrite;状态图标(● 完成 · ◐ 进行中 · ○ 待办)在折叠态与展开态保持一致,通过工具渲染器与 ACP plan 渲染器共用的TodoView组件实现。为什么需要
在 web-shell 的 daemon 路径下,todo 更新是以
todo_write工具调用(kind: "think")进来的,但内联渲染判定写死了字面量todowrite,所以 todo 专属 UI 从未命中。结果 todo 只渲染成一行普通工具,和相邻工具挤在同一个分组盒子里——既难扫读也无法折叠。按名字修正识别后,恢复了富渲染,并顺带让此前在 daemon 路径一直为空的浮动 todo 面板也能显示。评审验证计划
如何验证
用使用了 todo 工具的会话运行 web-shell:
npm run dev:daemon,打开浏览器,然后 resume 或新开一个模型会调用todo_write的会话。预期:每次 todo 更新显示为独立的TodoWrite N/M块,默认折叠到最新变化 / 当前步骤,并与其它工具调用分开;点击展开为完整编号清单;折叠态与展开态使用相同的 ● / ◐ / ○ 图标。自动化:在packages/web-shell跑npm test(273 通过),包含一个用真实todo_write(线上名 +thinkkind)渲染的新组件测试,以及一个断言todo_write独立成组、不并入相邻组的新 transcript 测试。前后对比证据
之前——一行扁平、不可折叠、与其它工具混在一组:
之后——独立的块,默认折叠到当前变化:
之后——点击展开为完整清单:
测试平台
环境(可选)
macOS 上本地
vitest单元/组件测试 + 生产构建(npm run build,app + lib);浏览器行为由渲染真实工具的组件测试覆盖,并通过npm run dev:daemon手动确认。风险与范围
todo_write工具调用的渲染与分组方式(每次更新现在独立成组而非合并)。已由单元 + 组件 + transcript 测试覆盖;无数据或 API 改动。关联 Issue
N/A —— 无关联 issue。