Skip to content

Surface TodoWrite task list per session (core, web, vscode) #155

@DrumRobot

Description

@DrumRobot

Summary

Claude Code records the live TodoWrite task list at ~/.claude/tasks/<sessionId>/*.json, but the session viewer currently has no way to surface those records. Add first-class read support to core, expose them via the web API, and render them next to the existing todo files so users can see the agent's in-flight plan and check progress (pending → in_progress → completed) at a glance.

This complements the existing Todo integration (packages/core/src/todos.ts, ~/.claude/todos/*.json) but targets a different data source: tasks are stateful, ordered, and carry blocking relationships, whereas todos are flat checklists.

Storage layout (Claude Code, observed)

~/.claude/tasks/
  <sessionId>/
    .lock
    .highwatermark        # single number: highest task id assigned
    <N>.json              # one file per task, integer N matching the id

Sample task JSON:

{
  "id": "11",
  "subject": "Implement search endpoint",
  "description": "Add /api/search returning paginated results.",
  "status": "pending",
  "blocks": [],
  "blockedBy": []
}

Notes from observation:

  • id is a stringified integer assigned monotonically; .highwatermark mirrors the largest id used.
  • status observed values: pending, in_progress, completed. Treat any other value as pending defensively.
  • blocks and blockedBy are arrays of task ids (strings). Cycles should not be possible but the renderer should handle them gracefully.
  • Files may appear or disappear mid-session as the agent reorganises; reads must tolerate missing entries.

Core changes (packages/core)

  1. paths.ts: add getTasksDir() returning ~/.claude/tasks.
  2. types.ts: add
    export interface TaskItem {
      id: string
      subject: string
      description: string
      status: 'pending' | 'in_progress' | 'completed'
      blocks: string[]
      blockedBy: string[]
    }
    export interface SessionTasks {
      sessionId: string
      tasks: TaskItem[]
      highWatermark: number
      hasTasks: boolean
    }
    and extend SessionData (around line 377) with tasks: SessionTasks next to todos: SessionTodos.
  3. New tasks.ts mirroring the surface of todos.ts:
    • findSessionTasks(sessionId)Effect<SessionTasks>. Lists <id>.json files, parses defensively, sorts by numeric id ascending.
    • sessionHasTasks(sessionId)Effect<boolean> quick check.
    • findOrphanTaskDirs()Effect<string[]> returning session ids with task directories whose session no longer exists. Mirror findOrphanTodos semantics.
    • deleteOrphanTaskDirs()Effect<{ success: boolean; deletedCount: number }> moving orphaned dirs into ~/.claude/tasks/.bak/<sessionId>/.
  4. Tests in packages/core/src/__tests__/tasks.test.ts covering: empty dir, mixed valid/invalid JSON, status normalisation, orphan detection, backup move.

Web changes (packages/web)

  1. API: extend the existing session detail endpoint (or add GET /api/session/[id]/tasks) to return SessionTasks so the viewer can render lazily.
  2. UI:
    • Add a Tasks panel to SessionViewer (collapsed by default if hasTasks === false).
    • Render a flat list grouped by status (in_progress first, then pending, then completed).
    • Show subject as the primary text, description truncated as secondary text.
    • Render blockedBy ids as small chips that link to the corresponding task row (in-page anchor).
  3. Storybook story for the panel: Empty, Mixed statuses, With blockedBy chains.
  4. Playwright e2e: load a fixture session that has tasks, assert the panel renders and status grouping is correct.

VSCode extension changes (packages/vscode-extension)

  1. treeProvider.ts: when a session has hasTasks, expose a child node group Tasks under the session node. Each task becomes a leaf with subject as label and status as description (in_progress/pending/completed).
  2. Tree icons: reuse Codicon circle-outline/sync~spin/check for pending/in_progress/completed.
  3. Hover: include description and blockedBy ids.
  4. No commands required for v0.5.0 — read-only display only.

Storybook

Required stories under packages/web/src/lib/components/:

  • TaskList.stories.svelte
    • Default — three tasks, one of each status
    • EmptyhasTasks === false, panel hidden
    • BlockedByChain — task B blockedBy: ['A'], task C blockedBy: ['B']

Verification / Test plan

Feature Procedure Expected Result
findSessionTasks parsing Run pnpm --filter core test tasks against fixture dir with valid/invalid JSON Valid tasks parsed and sorted by numeric id; invalid entries skipped silently
Status normalisation Feed task JSON with status: "unknown" Returned TaskItem.status === 'pending'
Orphan detection / backup Create task dir for a missing session id, run deleteOrphanTaskDirs() Returns deletedCount: 1; dir moved under ~/.claude/tasks/.bak/<sessionId>/
Web API endpoint curl GET /api/session/<id>/tasks against dev server with fixture session 200 with SessionTasks JSON shape (tasks[], highWatermark, hasTasks)
Web UI rendering Playwright loads fixture session in SessionViewer Tasks panel visible, three groups in order in_progress → pending → completed, blockedBy chips link to anchors
Storybook stories pnpm --filter web storybook then visit TaskList Default, Empty, BlockedByChain all render without console errors
VSCode tree Open fixture session in extension dev host Session node has child group Tasks with leaves matching task subjects + status icons

Out of scope

  • Editing tasks from within claude-code-sessions (read-only display only).
  • Cross-session task aggregation (e.g. global Tasks dashboard).
  • Notifying on status change (live updates can be a follow-up — initial implementation can re-read on viewer mount).

References

  • Existing parallel surface: packages/core/src/todos.ts, packages/core/src/types.ts (TodoItem, SessionTodos), packages/core/src/paths.ts (getTodosDir).
  • Storage layout reference: ~/.claude/tasks/<sessionId>/{<N>.json,.highwatermark,.lock}.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions