feat(tui): real-time per-file write activity panel in RunScreen#371
feat(tui): real-time per-file write activity panel in RunScreen#371
Conversation
🧙 Wizard CIRun 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:
Test all apps in a directory:
Test an individual app:
Show more apps
Results will be posted here when complete. |
There was a problem hiding this comment.
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.
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.
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>
549c4b1 to
ee34618
Compare
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>
|
bugbot run |
There was a problem hiding this comment.
✅ 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.

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
Tests
cc @amplitude/growth
Test plan
🤖 Generated with Claude Code
Note
Medium Risk
Touches the inner-agent hook plumbing and
WizardUIinterface, 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
WizardUImethods (recordFileChangePlanned/recordFileChangeApplied) instead of only emitting NDJSON in--agentmode.Introduces a
FileWritesPanelonRunScreenbacked by newWizardStore.fileWritesstate (with dedupe, orphan-apply handling, and FIFO eviction) and implements the new interface inInkUI,AgentUI(delegating to existing NDJSON emitters), andLoggingUI(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.