Skip to content

Visual Todo Lists#9

Merged
Fishy49 merged 4 commits intomainfrom
feature/visual-todo-lists
Apr 21, 2026
Merged

Visual Todo Lists#9
Fishy49 merged 4 commits intomainfrom
feature/visual-todo-lists

Conversation

@Fishy49
Copy link
Copy Markdown
Owner

@Fishy49 Fishy49 commented Apr 20, 2026

Summary

  • Adds a persistent, real-time visual todo list panel to each run step, extracted directly from Claude's TodoWrite tool calls in the stream log
  • Extends StreamLogHelper with three new public helpers (latest_todo_list, todo_status_icon, todo_status_class) and a TodoWrite branch in tool_use_label
  • Wires the new _todo_list partial into _run_step so it automatically appears during live execution (via Turbo Stream) and persists after the run completes

Implementation Details

The Problem

Claude Code agents use the TodoWrite tool 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

StreamLogHelper additions (app/helpers/stream_log_helper.rb)

  • latest_todo_list(stream_log) — scans the stream log in reverse chronological order to find the most recent TodoWrite tool call. This means it always shows the agent's current todo state, not a stale earlier snapshot. Returns nil when no TodoWrite has been issued.
  • todo_status_icon(status) — maps completed → ✓, 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_label now has a TodoWrite branch 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 to TOOL_ICONS.

_todo_list partial (app/views/runs/_todo_list.html.erb)

  • Self-guarding: if latest_todo_list returns nil (no TodoWrite seen yet), the partial renders nothing at all.
  • Shows a header with completion progress (completed/total).
  • Renders each todo with its status glyph and appropriate styling. For in-progress items, it prefers the activeForm field (Claude's present-tense description of what it is doing right now) over the static content field.
  • Uses bg-surface so the panel has visible contrast against its bg-surface-card parent container (matches the sibling stream_log panel).
  • Scoped to id="todo_list_<run_step.id>" so it participates correctly in Turbo Stream updates.

_run_step partial (app/views/runs/_run_step.html.erb)

  • One line added between the input variables block and the activity log block: <%= render "runs/todo_list", run_step: run_step %>
  • No conditional needed — the partial self-guards.
  • Because this lives inside the run_step_<id> DOM element that existing Turbo Stream broadcast_step replaces, the todo list updates in real time as the agent progresses, then persists in the persisted stream_log column when the step finishes.

Fixture (test/fixtures/run_steps.yml, test/fixtures/runs.yml)

  • Added todo_run — a dedicated run fixture so this step's cost/token aggregation cannot pollute completed_run's expected aggregate totals.
  • Added passed_step_with_todos on todo_run at position 1.
  • Stream log includes: a system event, two sequential TodoWrite assistant events (the first with one "Old" pending todo, the second with three todos at mixed completion states), and a result event.
  • This fixture exercises the "latest wins" reversal logic and the activeForm display 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 nil
  • todo_status_icon — three statuses return pairwise-distinct non-empty strings; unknown status falls back to pending icon
  • todo_status_class — verified Tailwind classes per status (completed: text-success line-through, in_progress: text-accent font-semibold, pending: text-content-muted)
  • tool_use_label for TodoWrite — non-empty todos produce a formatted summary string; empty todos fall back to input.to_s.truncate(80)
  • tool_icon_classTodoWrite maps to check-square
  • Partial render — renders _run_step with the fixture; asserts todo_list_<id> is present, "Review plan" and "Writing plan" (activeForm) are shown, "Old" is absent, and "activity log" still renders
  • stream_log_entries — asserts the activity log still captures both TodoWrite tool_use entries, with the latest having 3 todos

Notes

  • The activeForm field 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_list scans in reverse so performance is proportional to how far back the last TodoWrite is, not total log length. Uses reverse.find per event to pick the last matching block without building an intermediate filtered array.
  • No database migrations required — stream_log is an existing JSON column.

Review Fixes

Applied during code review (commit a4af8d4):

  • Test isolation — the original fixture attached passed_step_with_todos to completed_run, which caused its $0.02 / 10k tokens / 4 turns to pollute completed_run's usage_stats aggregate. That broke RunTest#test_usage_stats_aggregates_run_step_stats (expected $0.0523) and the RunsTest#view run with usage stats system test (asserts $0.05 / 5 turns). The fixture now lives on a dedicated todo_run so no other test is affected.
  • Styling contrast — the todo panel originally used bg-surface-card, the same token as its parent container (show.html.erb Steps panel), so the panel was visually indistinguishable from its background. Switched to bg-surface to match the contrast used by the sibling _stream_log panel.
  • Rubocop cleanup — removed offenses introduced by this PR: Performance/Detect (replaced content.select {...}.last with content.reverse.find {...}), Rails/Pluck, Layout/LineLength, and Metrics/BlockLength. Added Metrics/ModuleLength exclude for stream_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

Fishy49 and others added 3 commits April 20, 2026 15:21
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>
@Fishy49 Fishy49 marked this pull request as ready for review April 20, 2026 20:46
@Fishy49 Fishy49 merged commit 467d29a into main Apr 21, 2026
4 checks passed
@Fishy49 Fishy49 deleted the feature/visual-todo-lists branch April 21, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant