Skip to content

design: TUI dashboard — 5 views, workers, bottlenecks, queue, task detail - #72

Merged
Exelord merged 4 commits into
mainfrom
claude/tui-spec
May 13, 2026
Merged

design: TUI dashboard — 5 views, workers, bottlenecks, queue, task detail#72
Exelord merged 4 commits into
mainfrom
claude/tui-spec

Conversation

@Exelord

@Exelord Exelord commented May 13, 2026

Copy link
Copy Markdown
Member

Summary

Specs + architect's design doc for an opt-in --tui mode that turns vx run into a full-screen interactive dashboard. Design only — no source changes. Phase 1 implementation lands in a follow-up PR after the spec is approved.

What's in the PR

  • docs/design/tui.md (911 lines) — the spec.
  • docs/design/tui-design.md (788 lines) — the architect's design doc with concrete decisions.

Vision

opencode / k9s / lazygit level. Five top-level views switchable with 1-5, plus a Task Detail overlay on Enter. Live everything: sparklines, Gantt timeline, worker occupancy heatmaps, parallelization gauge, critical path, queue depth, historical stats per task.

The five views

  1. Overview — task list + stats + cache + remote-cache + timeline + live log + progress bar. Default.
  2. Graph — full-screen project DAG (indented topological tree, cross-project ▶ arrows, per-task progress bars, t toggles critical-path overlay).
  3. Workers — N concurrency slots with what each is running, per-slot 30s busy/idle heatmap, utilization sparkline, parallelization % gauge.
  4. Bottlenecks — critical path, tasks blocking the most dependents, slow-vs-historical callouts, cache-miss impact ranking.
  5. Queue — ready-but-waiting vs blocked-by-deps + queue-throughput sparkline.

Plus the Task Detail overlay (Enter from any view): command, hash, inputs, outputs, deps, dependents, historical stats from cache.db's runs table (last N runs, avg/p50/p99, success rate, cache-hit rate), estimated progress bar with ETA, live log pane.

First-class group + persistent rendering

  • Group tasks () roll up child status, Space-expandable.
  • Persistent tasks () show "ready since X" badge, header counter ⚡<n>.

Top architect decisions

  • Ink, not hand-roll. Hand-roll is 2-4 weeks for a worse v1. One file imports Ink (src/tui/tui.ts); everything else goes through a ink-shim.ts so the renderer is one-file replaceable.
  • Gate-check before Phase 1 merge: bun build --compile + Ink has a known yoga.wasm resolution bug (bun#13552). 30-minute experiment first.
  • Observer interface with one emit(event) method + tagged-union events. makeSafeObserver wrapper so a throwing observer cannot fail the run. LayeredCache decoupled via a new onRemoteRequest callback in LayeredCacheOptions.
  • --tui opt-in for Phases 1-2. Auto-promote (when TTY + !NO_COLOR + !CI + interactive + ≥80×20) only after Phase 3 ships and survives a release cycle. Default-on with a half-finished TUI is how we lose CI users.
  • Single store, multiple view components under src/tui/views/{overview,graph,workers,bottlenecks,queue}.tsx. Active view in store state; 1-5 keys dispatch viewChange. Per-view filter state.
  • Worker slot allocation via a small scheduler refactor: runGraph allocates from a lowest-free-index free-list and passes slot: number to execute(). Pure addition.
  • Historical stats batched once at runStart via a SQL query in prepareRun(), threaded through RunStartEvent. Available to every Observer consumer, not just the TUI.
  • Critical path computed via topo-order DP, recomputed on every taskComplete (O(V+E), microseconds at ≤1000 nodes).

Rollout phases

  • Phase 1--tui flag + Overview-only with Header/TaskList/LogPane/ProgressBar/StatusBar. Observer surface + scheduler slot allocation. ~2 weeks.
  • Phase 2 — Stats/Cache/RemoteCache/Timeline panels + group + persistent rendering + filter.
  • Phase 3 — Views 2-5 (Graph/Workers/Bottlenecks/Queue) + Task Detail overlay + historical-stats SQL + critical path. The "wow" phase.
  • Phase 4 — Polish: help overlay, auto-promotion, snapshot tests, critical-path t overlay.
  • Phase 5 (future) — mouse, re-run/kill from TUI, vx ui historical-runs browser, pause/resume, true 2D DAG.

Implementation order (from §11 of design doc)

  1. Scheduler slot allocation (+ Observer taskStart.slot).
  2. New Observer event cacheProbe.
  3. Batched history SQL in prepareRun + HistoryTable plumbed into RunStartEvent.
  4. State-shape additions; reducer handlers for the new events/actions.
  5. Selectors: selectParallelPct, selectCriticalPath, selectTopBlockers, selectSlowVsHistory, selectCacheMissImpact, ready/blocked queue selectors.
  6. View components in src/tui/views/.
  7. Task Detail + Help overlays.
  8. Critical-path recompute hook on taskComplete.
  9. 1 Hz sampler tick (combined: sparklines + heatmap + queue throughput).

Test plan

  • Spec written; architect pass complete.
  • No source changes; CI green (no behavior change to verify).
  • Phase 1 implementation in a follow-up PR after spec approval.

Generated by Claude Code

claude added 4 commits May 13, 2026 11:41
Spec for an opt-in --tui mode that turns vx run into a full-screen
interactive dashboard: live task list with status icons, focusable
per-task log pane, throughput/CPU/RSS sparklines, cache + remote-
cache stats, Gantt-style timeline, keyboard navigation, fallback
to framed-block output when not a TTY.

Sections:
- Vision + goals + non-goals
- Visual layout (80x24 minimum) with annotated panels
- Status icons, status accents, focus model
- Stats / cache / remote-cache / timeline / log / progress / status
  bar panels
- Keymap v1 (q, Tab, arrows, Enter, Esc, /, g, ?, pgup/pgdn)
- Data model — typed event stream from orchestrator
- Architecture — Ink (recommended), src/tui/ module layout,
  Observer interface added to RunOptions alongside Logger
- Activation rules — explicit --tui or auto-promote when
  TTY + !NO_COLOR + !CI + interactive + >= 80x24
- Lifecycle (init, render loop @ 30Hz cap, tear-down to alternate
  screen + print standard summary)
- Performance — 30Hz render cap, 10k-line per-task log cap, ring
  buffers for sparklines, decoupled event ingest
- Fallback — every disqualifying env enumerated
- Chart design — Unicode block sparkline, Gantt timeline math
- Help overlay
- Testing — ink-testing-library snapshots + table-driven pure-
  function tests
- Rollout — 4 phases
- 7 open questions for the architect

Architect pass will produce docs/design/tui-design.md with the
concrete answers + Observer event shapes + reducer architecture +
fallback decision + lifecycle details + testing strategy + risks.
… workers, bottlenecks, queue, task detail

Spec expansion + companion architect design doc.

## Spec (docs/design/tui.md) — extended

Original spec covered a single dashboard layout. Extended to five
top-level views (`1`-`5` keys):

- Overview — the original dashboard.
- Graph — full-screen project DAG (indented topological tree with
  cross-project ▶ arrows, status icons, per-task progress bars).
- Workers — N concurrency slots with per-slot 30s heatmap of
  busy/idle, utilization-over-time sparkline, parallelization %
  gauge.
- Bottlenecks — critical path (longest dependency chain by
  duration), tasks blocking the most dependents, slow-vs-historical
  callouts, cache-miss impact ranking.
- Queue — ready-but-waiting vs blocked-by-deps, queue-throughput
  sparkline.

Plus a Task Detail overlay (Enter from any view): full task
introspection with command, hash, inputs, outputs, deps, dependents,
historical stats from cache.db's runs table (last N runs, avg, p50,
p99, success rate, cache-hit rate), estimated progress bar with ETA,
live log pane.

Group tasks (▣ icon) and persistent tasks (⚡ icon) get first-class
rendering — groups roll up child status and are Space-expandable;
persistent show "ready since X" badges and a header ⚡<n> counter.

Header gains a parallel <P>% gauge (`floor(running / capacity * 100)`)
with threshold-tinted color (green ≥80, yellow 50-79, red <50).

Data-model section grew to include: worker slot map, per-slot 30-sample
heatmap ring buffers, historical-stats cache (batched SQL at runStart),
critical-path computation (recomputed each taskComplete), group rollup
state.

10 open questions for the architect (#1-3 from original, #4-10 new).

## Design doc (docs/design/tui-design.md) — appended §11

Architect pass returned with concrete decisions for the extended
scope:

- Historical stats: ONE batched SQLite query in prepareRun() returning
  a HistoryTable threaded into RunStartEvent. Capped at 50 rows per
  (project, task); client-side slice to 10 for Task Detail. Available
  to every Observer consumer, not just the TUI. Never-run tasks
  render `▱▱▱▱ ?`.
- Critical path: topo-order DP recomputed on every taskComplete
  (O(V+E), microseconds at ≤1000 nodes — no throttling needed).
  Persistent tasks excluded.
- Worker slots: runGraph allocates from a lowest-free-index free-list;
  execute() callback gains slot: number; Observer.taskStart grows the
  same field. Small scheduler change.
- Heatmap sampler: TUI-owned setInterval(1000ms) combined with the
  existing 1 Hz sparkline tick. Uint8Array(30) per slot.
- Multi-view: single store, state.activeView switches between view
  components under src/tui/views/. Task Detail + Help under
  src/tui/overlays/. Filter state per-view (filtering "test" in
  Workers is incoherent).
- Graph view: indented topo tree, cross-project deps inline. Critical-
  path overlay is a left-margin marker.
- Bottlenecks: four pure selectors. New Observer event cacheProbe
  fired from execute-task.ts populates cacheStatus for the cache-miss-
  impact ranking.
- Queue: ready vs blocked via dep-status predicate. Throughput
  sparkline counts waiting→ready transitions per second.
- Groups: transitive children precomputed at runStart; reverse-walk
  updates rollup counters on taskComplete. Per-group expanded flag
  survives view switches.

Implementation order in §11: scheduler slot allocation first
(unblocks the Observer event), then cacheProbe event, then batched
history SQL, then state/reducer extensions, then selectors, then
view components, then overlays, then critical-path recompute, then
the 1 Hz sampler.

No source changes in this commit — design only. Phase 1 implementation
will land in a separate PR.
User decision: use OpenTUI. Updates both spec and design doc to
reflect the renderer change. Ink remains documented as the
fallback if the OpenTUI bun-compile gate fails.

## Why OpenTUI over Ink

- Bun-first by design — renderer is a small native lib invoked via
  bun:ffi. No React-reconciler + Yoga (WASM) layout pass per frame;
  diff-based partial redraws happen in native code.
- Sidesteps the known yoga.wasm bun-compile bug (bun#13552 /
  bun#2034) that Ink hits inside a compiled binary.
- Built by the opencode team — its primitives are tuned for the
  same use case the spec calls "tiny stark level god."
- React ergonomics preserved via @opentui/react; component-tree
  mental model is identical to Ink.

## What changed in the spec (tui.md)

- Library-choice subsection rewritten with OpenTUI rationale.
- New "Compile-gate experiment" subsection with two paths (sibling-
  file install vs embed+extract shim) to validate bun build --compile
  works with the native lib before merging Phase 1.
- Module layout renames `App.tsx`'s import path: a new
  `src/tui/tui-shim.ts` is the only place that imports
  `@opentui/react`. All other components import from the shim — so
  swapping to Ink later is one file.
- Lifecycle step 2 says "Mount the OpenTUI app rooted at <App />
  via @opentui/react"; step 3 explains the renderer does native
  partial redraws (still throttle React component churn at 30 Hz).
- Testing section rewritten: pure-function layer is unchanged
  (reducer, selectors, sparkline math). Component-layer plan
  depends on whether @OpenTui ships a frame-capture testing
  helper; if not, we write a ~50-line stub renderer.
- Open questions #1, #13, #14 updated for OpenTUI.

## What changed in the design doc (tui-design.md)

- §1 question 7 answers "Bun compile + OpenTUI" with the native-lib
  bundling decision instead of yoga.wasm.
- §2 rewritten as "OpenTUI vs Ink vs hand-roll":
  - Recommendation: OpenTUI; Ink fallback if compile-gate fails for
    both bundling paths.
  - Why-OpenTUI-over-Ink rationale.
  - Why-Ink-stays-the-fallback (battle-tested, rich ecosystem).
  - Compile-gate experiment expanded: try sibling-file install
    first, then embed+extract. 30-60 min prototype.
  - Startup cost estimate revised: ~30-60ms cold (faster than Ink's
    ~80-120ms).
  - Bundle size: ~150-250 KB JS + 200-400 KB native lib per arch =
    ~400-650 KB compiled-binary contribution. Comparable to Ink.
- Sed-swept remaining Ink references throughout the rest of the doc
  (Observer wiring, store/reducer notes, lifecycle, resize, testing,
  module layout, extension-point notes) → either reworded as
  renderer-agnostic or explicitly OpenTUI.

## What's unchanged

The Observer surface, store / reducer architecture, fallback
decision (`shouldUseTui`), lifecycle (alt-screen, SIGINT, runEnd),
multi-view structure, all 5 views, Task Detail overlay,
historical-stats SQL pull, critical-path computation, worker-slot
allocation in the scheduler, and the implementation order are
unchanged — they're all renderer-agnostic by design.

The compile-gate experiment is the gate before merging Phase 1.
If OpenTUI's native lib can't be bundled cleanly (neither sibling-
file nor embed+extract works), Ink is the well-trodden fallback.

414 tests still pass; format + lint clean.
@Exelord
Exelord merged commit e3d528c into main May 13, 2026
1 check passed
@Exelord
Exelord deleted the claude/tui-spec branch May 13, 2026 12:18
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