Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move passed_step_with_todos fixture to new todo_run to prevent its stats polluting completed_run's usage aggregation (was breaking RunTest#usage_stats_aggregates_run_step_stats and the runs system test that asserts $0.05 / 5 turns) - Use bg-surface (not bg-surface-card) on the todo panel so it has visible contrast against its bg-surface-card parent container, matching the sibling stream_log panel - Address rubocop offenses introduced by the feature: Performance/Detect (reverse.find), Rails/Pluck, Layout/LineLength, Metrics/BlockLength, and add ModuleLength exclude for the grown helper Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
TodoWritetool calls in the stream logStreamLogHelperwith three new public helpers (latest_todo_list,todo_status_icon,todo_status_class) and aTodoWritebranch intool_use_label_todo_listpartial into_run_stepso it automatically appears during live execution (via Turbo Stream) and persists after the run completesImplementation Details
The Problem
Claude Code agents use the
TodoWritetool to manage their working task list. Previously, this information was only visible deep inside the activity log as a raw tool call — users had no at-a-glance way to see what the agent was working on or how far along it was.How It Works
StreamLogHelperadditions (app/helpers/stream_log_helper.rb)latest_todo_list(stream_log)— scans the stream log in reverse chronological order to find the most recentTodoWritetool call. This means it always shows the agent's current todo state, not a stale earlier snapshot. Returnsnilwhen no TodoWrite has been issued.todo_status_icon(status)— mapscompleted → ✓,in_progress → ▸,pending → ○. Unknown statuses fall back to the pending glyph.todo_status_class(status)— returns Tailwind classes per status: completed items get strikethrough (text-success line-through), in-progress items are emphasized (text-accent font-semibold), pending items are muted (text-content-muted).tool_use_labelnow has aTodoWritebranch that produces a compact summary like "3 todos (1 in progress, 1 pending, 1 completed)" so the activity log entry is also informative."TodoWrite" => "check-square"added toTOOL_ICONS._todo_listpartial (app/views/runs/_todo_list.html.erb)latest_todo_listreturns nil (no TodoWrite seen yet), the partial renders nothing at all.(completed/total).activeFormfield (Claude's present-tense description of what it is doing right now) over the staticcontentfield.bg-surfaceso the panel has visible contrast against itsbg-surface-cardparent container (matches the sibling stream_log panel).id="todo_list_<run_step.id>"so it participates correctly in Turbo Stream updates._run_steppartial (app/views/runs/_run_step.html.erb)<%= render "runs/todo_list", run_step: run_step %>run_step_<id>DOM element that existing Turbo Streambroadcast_stepreplaces, the todo list updates in real time as the agent progresses, then persists in the persistedstream_logcolumn when the step finishes.Fixture (
test/fixtures/run_steps.yml,test/fixtures/runs.yml)todo_run— a dedicated run fixture so this step's cost/token aggregation cannot pollutecompleted_run's expected aggregate totals.passed_step_with_todosontodo_runat position 1.TodoWriteassistant events (the first with one "Old" pending todo, the second with three todos at mixed completion states), and a result event.activeFormdisplay path.Testing Plan
All tests are in
test/helpers/stream_log_helper_test.rb(ActionView::TestCase):latest_todo_list— single event returns correct todos; multiple events collapse to the latest (stale "Old" entry excluded); missing TodoWrite returns nil; nil/non-array/empty-todos inputs all return niltodo_status_icon— three statuses return pairwise-distinct non-empty strings; unknown status falls back to pending icontodo_status_class— verified Tailwind classes per status (completed:text-success line-through, in_progress:text-accent font-semibold, pending:text-content-muted)tool_use_labelfor TodoWrite — non-empty todos produce a formatted summary string; empty todos fall back toinput.to_s.truncate(80)tool_icon_class—TodoWritemaps tocheck-square_run_stepwith the fixture; assertstodo_list_<id>is present, "Review plan" and "Writing plan" (activeForm) are shown, "Old" is absent, and "activity log" still rendersstream_log_entries— asserts the activity log still captures both TodoWrite tool_use entries, with the latest having 3 todosNotes
activeFormfield is Claude Code's present-progressive description of what the agent is actively doing on a task (e.g., "Writing plan" vs the static label "Write plan"). Showing it for in-progress items makes the UI read naturally as a live status indicator.latest_todo_listscans in reverse so performance is proportional to how far back the last TodoWrite is, not total log length. Usesreverse.findper event to pick the last matching block without building an intermediate filtered array.stream_logis an existing JSON column.Review Fixes
Applied during code review (commit
a4af8d4):passed_step_with_todostocompleted_run, which caused its$0.02 / 10k tokens / 4 turnsto pollutecompleted_run'susage_statsaggregate. That brokeRunTest#test_usage_stats_aggregates_run_step_stats(expected$0.0523) and theRunsTest#view run with usage statssystem test (asserts$0.05/5 turns). The fixture now lives on a dedicatedtodo_runso no other test is affected.bg-surface-card, the same token as its parent container (show.html.erbSteps panel), so the panel was visually indistinguishable from its background. Switched tobg-surfaceto match the contrast used by the sibling_stream_logpanel.Performance/Detect(replacedcontent.select {...}.lastwithcontent.reverse.find {...}),Rails/Pluck,Layout/LineLength, andMetrics/BlockLength. AddedMetrics/ModuleLengthexclude forstream_log_helper.rb(the helper has grown past 100 lines as a natural consequence of this feature; file-specific exclusions are already the project's convention for other Metrics cops).🤖 Generated with Claude Code