fix(tui): TaskCreate/TaskUpdate render nothing inline, like TodoWrite - #837
Merged
Conversation
The checklist HUD (#827) landed but the TaskV2 mutations kept printing their raw results underneath it, so planning a turn cost a screenful of JSON restating the checklist: one `⏺ TaskCreate(…) ⎿ {"task":{"id":…}}` row per task up front, then one `⏺ TaskUpdate ⎿ {"success":true,…,"statusChange":{…}}` row per status flip. TodoWrite was already suppressed for exactly this reason — the pinned checklist is these tools' entire UI — and TaskV2 is the same tool in a per-row shape, so it gets the same treatment: recordTodos() has already fed the HUD from this very call by the time the trail line would be appended. Suppression is by outcome, not just by name. tasks_v2.py answers a refused mutation with a *successful* tool call carrying `{"success": false, …, "error": "Task not found"}` (`_task_update_call`), and the HUD then redraws the unchanged checklist — hiding that row would leave the miss with no trace anywhere in the UI. So isChecklistHudOnly() consults the result text, and a refused (or errored) mutation keeps its row. TaskList/TaskGet/TaskOutput keep theirs unconditionally: they are reads whose output (task bodies, background-shell logs) the HUD never shows. The live spinner row in ToolTrail gets the same filter, so a mutation that will render no completion line does not blink a row on the way either. Tests: 5 new vitest (turnController suppression + the refused/read paths, isChecklistHudTool/isChecklistHudOnly units). Verified against deepseek-v4-pro (deepseek provider) in a tmux-driven TUI: same prompt before and after, four TaskCreate + two TaskUpdate calls. Before, six JSON rows above the HUD; after, only the checklist. A deliberately wrong taskId still renders its "Task not found" row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Test Results 4 files 966 suites 26m 58s ⏱️ Results for commit 7ebbd99. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The task checklist HUD (#827) shipped, but the TaskV2 mutations kept printing their
raw results underneath it. Planning a turn therefore cost a screenful of JSON that
restated the checklist — one
⏺ TaskCreate(…) ⎿ {"task":{"id":…}}row per task upfront, then one
⏺ TaskUpdate ⎿ {"success":true,…,"statusChange":{…}}row per statusflip.
TodoWritewas already suppressed for exactly this reason (the pinned checklistis these tools' entire UI); TaskV2 is the same tool in a per-row shape, so it now gets
the same treatment.
recordTodos()has already fed the HUD from the very call whoserow is dropped.
Suppression is by outcome, not just by name.
tasks_v2.pyanswers a refusedmutation with a successful tool call carrying
{"success": false, …, "error": "Task not found"}(_task_update_call), and the HUDthen redraws the unchanged checklist — hiding that row would leave the miss with no
trace anywhere in the UI. So
isChecklistHudOnly()consults the result text, and arefused (or errored) mutation keeps its row.
TaskList/TaskGet/TaskOutputkeep their rows unconditionally: they are readswhose output (task bodies, background-shell logs) the HUD never shows.
The live spinner row in
ToolTrailgets the same filter, so a mutation that willrender no completion line doesn't blink a row on the way either.
Changes
ui-tui/src/lib/todo.ts—isChecklistHudTool()(TodoWrite/TaskCreate/TaskUpdate),checklistMutationRefused()(JSONsuccess:false, with a prose fallback forpre-JSON backends) and
isChecklistHudOnly().ui-tui/src/app/turnController.ts—recordToolCompleteswaps the hardcodedname !== 'TodoWrite'check forisChecklistHudOnly(name, error, resultText).ui-tui/src/components/thinking.tsx—ToolTrailfilters the live rows by thesame predicate.
Tests
5 new vitest cases (
todoSurface.test.ts,lib/todo.test.ts): suppression ofTaskCreate+TaskUpdate with the HUD still fed and no stranded spinner, a refused
TaskUpdate keeping its row, TaskList keeping its row, plus units for both predicates.
Suite:
1750 passed, 8 failed— the 8 failures are pre-existing onmain@5441d5d(verified by stashing this diff and re-running them: identical failures).
Verification — deepseek-v4-pro, tmux-driven TUI
Same prompt, same model, same workspace; only the branch differs.
BEFORE (
main@5441d5d)AFTER (this branch)
AFTER — a refused mutation still renders (
TaskCreatelanded and is HUD-only;the bogus
TaskUpdatekeeps its row)