design: TUI dashboard — 5 views, workers, bottlenecks, queue, task detail - #72
Merged
Conversation
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.
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Specs + architect's design doc for an opt-in
--tuimode that turnsvx runinto 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 onEnter. Live everything: sparklines, Gantt timeline, worker occupancy heatmaps, parallelization gauge, critical path, queue depth, historical stats per task.The five views
ttoggles critical-path overlay).Plus the Task Detail overlay (Enter from any view): command, hash, inputs, outputs, deps, dependents, historical stats from
cache.db'srunstable (last N runs, avg/p50/p99, success rate, cache-hit rate), estimated progress bar with ETA, live log pane.First-class group + persistent rendering
▣) roll up child status,Space-expandable.⚡) show "ready since X" badge, header counter⚡<n>.Top architect decisions
src/tui/tui.ts); everything else goes through aink-shim.tsso the renderer is one-file replaceable.bun build --compile+ Ink has a knownyoga.wasmresolution bug (bun#13552). 30-minute experiment first.emit(event)method + tagged-union events.makeSafeObserverwrapper so a throwing observer cannot fail the run.LayeredCachedecoupled via a newonRemoteRequestcallback inLayeredCacheOptions.--tuiopt-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.src/tui/views/{overview,graph,workers,bottlenecks,queue}.tsx. Active view in store state;1-5keys dispatchviewChange. Per-view filter state.runGraphallocates from a lowest-free-index free-list and passesslot: numbertoexecute(). Pure addition.runStartvia a SQL query inprepareRun(), threaded throughRunStartEvent. Available to every Observer consumer, not just the TUI.taskComplete(O(V+E), microseconds at ≤1000 nodes).Rollout phases
--tuiflag + Overview-only with Header/TaskList/LogPane/ProgressBar/StatusBar. Observer surface + scheduler slot allocation. ~2 weeks.toverlay.vx uihistorical-runs browser, pause/resume, true 2D DAG.Implementation order (from §11 of design doc)
slotallocation (+ ObservertaskStart.slot).cacheProbe.prepareRun+HistoryTableplumbed intoRunStartEvent.selectParallelPct,selectCriticalPath,selectTopBlockers,selectSlowVsHistory,selectCacheMissImpact, ready/blocked queue selectors.src/tui/views/.taskComplete.Test plan
Generated by Claude Code