Skip to content

feat(tui): Phase 2A — renderer-agnostic state + selectors + primitives - #74

Merged
Exelord merged 1 commit into
mainfrom
claude/tui-phase-2-renderer
May 13, 2026
Merged

feat(tui): Phase 2A — renderer-agnostic state + selectors + primitives#74
Exelord merged 1 commit into
mainfrom
claude/tui-phase-2-renderer

Conversation

@Exelord

@Exelord Exelord commented May 13, 2026

Copy link
Copy Markdown
Member

Summary

Phase 2A of the TUI: renderer-agnostic foundation. Pure functions
only, no JSX yet — every module is fully unit-tested. Phase 2B wires
the OpenTUI React components and the --tui CLI flag on top of this
state shape (zero reducer changes expected).

Tests: 434 → 495. New modules, all TDD-driven:

Module Purpose Tests
src/tui/should-use-tui.ts Pure decision-table for when TUI can run 11
src/tui/primitives/sparkline.ts Float32 ring buffer + ▁..▇ render 9
src/tui/primitives/timeline-layout.ts Gantt-bar layout math 9
src/tui/state/critical-path.ts Topo-DP longest path 8
src/tui/state/store.ts Reducer over event / tick / key actions 13
src/tui/state/selectors.ts parallelPct, queues, blockers, slow, miss 11

Notable design touches (from docs/design/tui-design.md)

  • Reducer is pure. State carries tasks (insertion = topo order),
    workerSlots mirror, three sparkline buffers, remote counters
    (1024-latency cap), per-view filters, overlay flags, dirty flag.
  • Log buffer splits chunks on \n, parks partial trailing in
    pendingLine, flushes on taskComplete. Capped at 10k lines with a
    drop-1k strategy + …N more lines elided… sentinel.
  • Slot mirror populates from taskStart.slot (Phase 1's
    scheduler change) and frees on taskComplete. Selectors compute
    parallelPct from workerSlots directly.
  • Critical-path weights: finished tasks use actualMs, running
    use currentElapsedMs, waiting/ready use historyAvgMs,
    skipped/failed are 0, persistent are excluded.
  • Sparkline render: lowest sample → , highest → . Full-block
    deliberately omitted (clips on some terminals per design doc).

What's NOT in this PR

  • The React components (Header, TaskList, LogPane, ProgressBar,
    StatusBar, and the 5 views).
  • The OpenTUI shim (src/tui/tui-shim.ts) and src/tui/tui.ts entry.
  • The --tui CLI flag and the compile-gate experiment.

All of that ships in Phase 2B against this branch's state shape.

Test plan

  • bun src/bin.ts run format — clean
  • bun src/bin.ts run lint — 0 warnings, 0 errors
  • bun src/bin.ts run test — 495 pass, 0 fail
  • New: 61 tests across 6 files (sparkline, timeline-layout,
    critical-path, should-use-tui, tui-store, tui-selectors)
  • All 434 prior tests still green

Dependencies

  • @opentui/core, @opentui/react added. No imports yet — they
    ship for Phase 2B's renderer. Lockfile updated.

https://claude.ai/code/session_016HXj6HW6bxSn8EYuKcxTD9


Generated by Claude Code

… primitives)

Pure-function TUI scaffolding. No JSX yet (Phase 2B); everything here
is testable in isolation. Phase 2B will wire OpenTUI components on top
of this state shape with no further reducer changes.

Six new modules, all TDD-driven (61 new tests, 434 → 495 total):

1. **`src/tui/should-use-tui.ts`** — pure decision-table predicate
   for when the alt-screen TUI can run. 11 tests over the env matrix
   from `docs/design/tui-design.md` §5: --no-tui, planning modes,
   non-TTY, NO_COLOR, CI, custom-logger, terminal-size, opt-in
   fall-through. Stable reason strings for "TUI unavailable (<reason>)".

2. **`src/tui/primitives/sparkline.ts`** — Float32Array ring buffer
   + unicode-block renderer (▁..▇). 9 tests cover wrap, default vs
   explicit width, padding, range-mapping, NaN/negative clamp.
   Three buffers per State (throughput, remote ops, parallel%); the
   1 Hz tick samples them all.

3. **`src/tui/primitives/timeline-layout.ts`** — Gantt-bar layout
   for the Timeline view. (startNs, endNs, totalNs) → (startCol,
   widthCols). 9 tests cover scale, 1-cell visibility floor, right-
   edge clamp, not-started skip, input order preservation.

4. **`src/tui/state/critical-path.ts`** — topo-DP for the longest
   path through the task DAG, weight chosen by status (actual /
   currentElapsed / historyAvg / 0 for skipped/failed). Persistent
   tasks excluded. 8 tests cover ties (earlier dep wins),
   parallel-chain max, running-task elapsed, predicted-history.

5. **`src/tui/state/store.ts`** — full reducer over Action =
   `{ type:'event' } | { type:'tick' } | { type:'key' }`. State carries
   tasks (insertion = topo order), workerSlots (mirror of scheduler
   allocation), history table, three sparkline buffers, remote
   counters (capped 1024 latencies), active view, focusPanel,
   selectedTaskId, filters per-view, taskDetailOpen, showHelp,
   dirty flag. 13 tests cover runStart init, taskStart/Complete slot
   mgmt, log buffering (split on \\n, pendingLine flush at end),
   cacheProbe, remoteCache counters + latency cap, runEnd, keys
   (viewChange/toggleHelp/selectTask), tick (sparkline sampling).
   Log buffer capped at 10k lines with N-elided sentinel.

6. **`src/tui/state/selectors.ts`** — pure (State) → result for the
   five Phase 2/3 views: parallelPct (header gauge), ready queue,
   blocked queue, topBlockers (by dependentsCount), slowVsHistory
   (>1.5× ratio), cacheMissImpact (ranked by history avg). 11 tests
   cover empty/edge cases.

Added: `@opentui/core` + `@opentui/react` to deps. No imports yet —
Phase 2B's `src/tui/tui.ts` (the single import site) lands with the
React components + the `--tui` CLI flag. The compile-gate experiment
documented in `tui-design.md` §2 runs there too.

https://claude.ai/code/session_016HXj6HW6bxSn8EYuKcxTD9
@Exelord
Exelord merged commit 56ed1cd into main May 13, 2026
1 check passed
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