Skip to content

feat(tui): real-time per-file write activity panel in RunScreen#371

Merged
kelsonpw merged 2 commits intomainfrom
omnara/tui-file-writes-panel
Apr 29, 2026
Merged

feat(tui): real-time per-file write activity panel in RunScreen#371
kelsonpw merged 2 commits intomainfrom
omnara/tui-file-writes-panel

Conversation

@kelsonpw
Copy link
Copy Markdown
Collaborator

@kelsonpw kelsonpw commented Apr 29, 2026

Summary

The inner Claude SDK agent writes files via Edit/Write/MultiEdit tool calls during a run, but the TUI never showed users which files were being touched in real time. From the run logs, the largest Ctrl+C cohort lives in the 30–90s gap between "Setup started" and the first task completion — users see a spinner with no file-level signal and assume the wizard hung.

The `file_change_planned` / `file_change_applied` NDJSON events from #270 already stream this data in agent mode. This PR routes them through the abstract WizardUI so InkUI can drive a TUI panel in parallel, with no schema change to the agent-mode envelope (schema v:1, outer-agent compatibility preserved).

What changed

  • New WizardUI contract: `recordFileChangePlanned` / `recordFileChangeApplied` on the abstract interface, with three implementations:
    • InkUI writes to a new `$fileWrites` nanostore atom on WizardStore.
    • AgentUI delegates to its existing `emitFileChangePlanned` / `emitFileChangeApplied` methods — NDJSON shape unchanged.
    • LoggingUI prints a single `◌ ` line per write.
  • inner-lifecycle.ts stops branching on `instanceof AgentUI` for file-change events. They go through `getUI()` so all three UI implementations can subscribe.
  • WizardStore gains `$fileWrites` with FIFO eviction at `MAX_FILE_WRITES=50`, defensive collapse of duplicate planned events from retry loops, and synthesis of orphan applied events when hooks arrive out of order.
  • FileWritesPanel renders below the Tasks list:
    • Per-row spinner / ✓ / ✗
    • Color by operation: CREATE green, MODIFY amber, DELETE red
    • Bytes + duration on apply (`512 bytes 1.2s`)
    • "generating… 2.5s" with elapsed time on in-flight rows
    • Header counter switches between `X/Y written` (in flight) and `N written` (settled)
    • Hidden until the first PreToolUse fires so RunScreen stays compact during planning
    • `maxVisible=8` cap so long-running runs don't push the rest of the dashboard off-screen

Tests

  • 5 new in `store.test.ts` — planned→applied flow with bytes+duration, orphan applied synthesis, dedup of back-to-back planned events, FIFO eviction at MAX_FILE_WRITES, multi-pass match-most-recent.
  • 9 new in `FileWritesPanel.test.tsx` — hidden when empty, path relativization, CREATE/MODIFY labels, generating/applied/failed states, header counter, `maxVisible` cap.
  • Full suite: 1999 passed (was 1914).

cc @amplitude/growth

Test plan

  • Run `pnpm try` against a small Next.js test app and confirm the Files panel populates in real time below the Tasks list.
  • Run `pnpm try --agent` against the same app and confirm `file_change_planned` / `file_change_applied` NDJSON events still appear with the same v:1 envelope (outer-agent backward-compat).
  • Confirm `/diagnostics` and other slash commands still work mid-run with the panel rendered.

🤖 Generated with Claude Code


Note

Medium Risk
Touches the inner-agent hook plumbing and WizardUI interface, so regressions could impact event emission across TUI/agent/CI modes, though the NDJSON schema is explicitly preserved and behavior is covered by new tests.

Overview
Adds real-time per-file write visibility to the TUI by routing inner-agent write-hook events through new WizardUI methods (recordFileChangePlanned / recordFileChangeApplied) instead of only emitting NDJSON in --agent mode.

Introduces a FileWritesPanel on RunScreen backed by new WizardStore.fileWrites state (with dedupe, orphan-apply handling, and FIFO eviction) and implements the new interface in InkUI, AgentUI (delegating to existing NDJSON emitters), and LoggingUI (single-line CI logging). Includes new unit tests for store semantics and panel rendering.

Reviewed by Cursor Bugbot for commit 357ded3. Bugbot is set up for automated code reviews on this repo. Configure here.

@kelsonpw kelsonpw requested a review from a team as a code owner April 29, 2026 14:29
@github-actions
Copy link
Copy Markdown
Contributor

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci django
  • /wizard-ci fastapi
  • /wizard-ci flask
  • /wizard-ci javascript-node
  • /wizard-ci javascript-web
  • /wizard-ci next-js
  • /wizard-ci python
  • /wizard-ci react-router
  • /wizard-ci vue

Test an individual app:

  • /wizard-ci django/django3-saas
  • /wizard-ci fastapi/fastapi3-ai-saas
  • /wizard-ci flask/flask3-social-media
Show more apps
  • /wizard-ci javascript-node/express-todo
  • /wizard-ci javascript-node/fastify-blog
  • /wizard-ci javascript-node/hono-links
  • /wizard-ci javascript-node/koa-notes
  • /wizard-ci javascript-node/native-http-contacts
  • /wizard-ci javascript-web/saas-dashboard
  • /wizard-ci next-js/15-app-router-saas
  • /wizard-ci next-js/15-app-router-todo
  • /wizard-ci next-js/15-pages-router-saas
  • /wizard-ci next-js/15-pages-router-todo
  • /wizard-ci python/meeting-summarizer
  • /wizard-ci react-router/react-router-v7-project
  • /wizard-ci react-router/rrv7-starter
  • /wizard-ci react-router/saas-template
  • /wizard-ci react-router/shopper
  • /wizard-ci vue/movies

Results will be posted here when complete.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Path prefix matching lacks directory boundary check
    • Added a directory boundary check ensuring the character after the installDir prefix is '/' (or the path equals installDir exactly), preventing false matches on paths like /proj-backup/ when installDir is /proj.

Create PR

Or push these changes by commenting:

@cursor push fa95304079
Preview (fa95304079)
diff --git a/src/ui/tui/components/FileWritesPanel.tsx b/src/ui/tui/components/FileWritesPanel.tsx
--- a/src/ui/tui/components/FileWritesPanel.tsx
+++ b/src/ui/tui/components/FileWritesPanel.tsx
@@ -69,7 +69,11 @@
  * possible — skill installs sometimes touch tmp dirs).
  */
 const displayPath = (raw: string, installDir?: string): string => {
-  if (installDir && raw.startsWith(installDir)) {
+  if (
+    installDir &&
+    raw.startsWith(installDir) &&
+    (raw.length === installDir.length || raw[installDir.length] === '/')
+  ) {
     const rel = path.relative(installDir, raw);
     return rel === '' ? path.basename(raw) : rel;
   }
@@ -172,7 +176,10 @@
     detail = 'failed';
   } else {
     const elapsed = now - entry.startedAt;
-    detail = elapsed >= 1000 ? `generating… ${formatDuration(elapsed)}` : 'generating…';
+    detail =
+      elapsed >= 1000
+        ? `generating… ${formatDuration(elapsed)}`
+        : 'generating…';
   }
 
   return (

You can send follow-ups to the cloud agent here.

Comment thread src/ui/tui/components/FileWritesPanel.tsx Outdated
The inner Claude SDK agent writes files via Edit/Write/MultiEdit tool
calls, but the TUI never showed users which files were being touched
in real time. Users staring at the Setup screen for 30-90s with no
file-level signal were the most common Ctrl+C cohort in the run logs.

The `file_change_planned` / `file_change_applied` NDJSON events from
PR #270 already stream this data in --agent mode. This PR routes them
through the abstract WizardUI so InkUI can drive a TUI panel in
parallel, with no schema change to the agent-mode envelope.

Architecture:
- New WizardUI methods recordFileChangePlanned / recordFileChangeApplied
  with implementations on InkUI (writes to nanostore atom), AgentUI
  (delegates to the existing emit* — NDJSON contract unchanged), and
  LoggingUI (single line per write in CI).
- WizardStore gains a $fileWrites atom with FIFO eviction at
  MAX_FILE_WRITES=50, defensive collapse of duplicate planned events,
  and synthesis of orphan applied events (out-of-order hooks).
- inner-lifecycle.ts stops branching on `instanceof AgentUI` for
  file_change events — they go through the abstract interface so all
  three UIs can subscribe.
- New FileWritesPanel component renders below the Tasks list with
  per-row spinner / checkmark / cross, color by operation
  (CREATE green, MODIFY amber, DELETE red), bytes + duration on
  apply, and "generating…" with elapsed time on in-flight rows.

Tests: 5 new in store.test.ts (planned-then-applied flow, orphan
synthesis, dedup, FIFO eviction, multi-pass match-most-recent),
9 new in FileWritesPanel.test.tsx (hidden-when-empty, path
relativization, op labels, generating/applied/failed states,
header counter, maxVisible cap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kelsonpw kelsonpw force-pushed the omnara/tui-file-writes-panel branch from 549c4b1 to ee34618 Compare April 29, 2026 15:13
A naive `startsWith(installDir)` check would falsely match sibling dirs
that share a prefix — e.g. `/proj-backup/file.ts` against installDir
`/proj` would render as `-backup/file.ts`. Require the next character
after the prefix to be a path separator (or the path to equal the
install dir exactly) so only true descendants get relativized.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kelsonpw
Copy link
Copy Markdown
Collaborator Author

bugbot run

@kelsonpw kelsonpw merged commit f736a71 into main Apr 29, 2026
10 of 11 checks passed
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 357ded3. Configure here.

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