Skip to content

Add chunk watch TUI dashboard with sidecar activity log - #446

Open
hanabel1 wants to merge 4 commits into
mainfrom
tui-dash
Open

Add chunk watch TUI dashboard with sidecar activity log#446
hanabel1 wants to merge 4 commits into
mainfrom
tui-dash

Conversation

@hanabel1

@hanabel1 hanabel1 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds chunk watch, a live TUI dashboard showing active sidecars, sync state, and recent activity in a split-pane layout.

  • --all flag watches all known projects, not just the current direcotyr
  • Sidecars are reused across sessions instead of spawning a new one per Claude Code session
  • Adds an append only JSONL event log (internal/eventlog) written by chunk sidecar sync and chunk validate to feed the TUI
  • Adds iostreamLevelError and emits done/error completion events from validate so the spinner clears correctly.
Screen.Recording.2026-07-23.at.3.44.56.PM.mov

Implementation

New packages:

  • internal/eventlog — append-only JSONL log; Log.Wrap decorates any StatusFunc to mirror calls into the log tagged with sidecar ID, op, and branch
  • internal/tui/watch — BubbleTea model: left pane lists sidecars with sync/running state, right pane shows events filtered to the selected sidecar grouped by op-run; polls every 2s and tails the log incrementally

Wiring changes:

  • internal/cmd/sidecar.go — wraps syncFn with the event log before passing to BundleSync/Sync
  • internal/cmd/validate.go — wraps statusFn with the event log before runValidate; emits LevelDone/LevelError after the run so the TUI knows when validation is complete
  • internal/cmd/root.go — registers newWatchCmd()
  • internal/iostream/status.go — adds LevelError constant
  • internal/ui/format.go — adds ErrFailure for red stderr rendering
  • go.mod — promotes charm.land/lipgloss/v2 from indirect to direct dependency

Test plan

  • task build && ./dist/chunk watch — opens TUI; shows sidecars, updates every 2s
  • ./dist/chunk sidecar sync — sync events appear in right pane for the active sidecar
  • ./dist/chunk validate — validate events appear; spinner clears after ✓ validate passed
  • Validate failure — spinner clears with ✗ validate failed in red
  • j/k — navigates sidecars; right pane updates to show selected sidecar's events
  • q / Ctrl-C — exits cleanly
  • task test — all tests pass

🤖 Generated with Claude Code

@michael-webster

Copy link
Copy Markdown
Contributor

🔥

Introduces `chunk watch`, a live BubbleTea dashboard showing active sidecars and their recent sync/validate activity. Adds an append-only JSONL event log written by `chunk sidecar sync` and `chunk validate` so the TUI has data to display.

Also adds `iostream.LevelError` and wires done/error completion events into validate so the TUI correctly reflects when an operation finishes rather than spinning indefinitely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread internal/tui/watch/model.go Outdated
Comment thread internal/cmd/sidecar.go
return fmt.Errorf("sync: %w", cwdErr)
}
syncFn := newStatusFunc(io)
if dataDir, dirErr := sidecar.StateDir(); dirErr == nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we duplicate this a few times, can you create a helper in the event log that closes over all the work to figure out the file path and handle any errors loading the sidecar

}
offset := m.offsets[i]
if offset == 0 {
recent, _ := p.Log.Recent(recentEvents)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we need to handle these errors at all?

Comment thread internal/tui/watch/model.go Outdated
branch := CurrentBranch(root)
return os.WriteFile(filepath.Join(dir, sidecarFileName(session.IDFromCtx(ctx), branch)), data, 0o644)
if err := os.WriteFile(filepath.Join(dir, sidecarFileName(session.IDFromCtx(ctx), branch)), data, 0o644); err != nil {
return err

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could saveDir() and config.ProjectDataDir() produce different subdirectory paths for the same project root? Both write a project-root breadcrumb, but AllProjectRoots discovers projects by iterating config.AppData() subdirs. If the two functions hash or clean the project path differently, projects registered via watch.go (which calls config.ProjectDataDir(abs) directly) would never appear in --all output — silently producing an empty list with no error.

- Add eventlog.WrapFromDir helper to consolidate the StateDir→Open→Wrap
  pattern duplicated across sync and validate commands
- Cache headRef and CurrentBranch in loadData so git subprocesses fire
  once per poll tick instead of on every render frame; bump pollInterval
  2s→5s
- Fix ProjectDataDir to resolve symlinks via EvalSymlinks before hashing,
  ensuring watch.go (git --show-toplevel) and sidecar.saveDir (filesystem
  walk) always land in the same data directory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

2 participants