Skip to content

feat(tui): Phase 2B — OpenTUI renderer + --tui CLI flag - #75

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

feat(tui): Phase 2B — OpenTUI renderer + --tui CLI flag#75
Exelord merged 1 commit into
mainfrom
claude/tui-phase-2b-renderer

Conversation

@Exelord

@Exelord Exelord commented May 13, 2026

Copy link
Copy Markdown
Member

Summary

Ships the minimum-viable interactive TUI promised by Phase 1's design
doc §8. Built on top of Phase 2A's pure-function foundation — zero
reducer/state/selector changes.
Single screen for now (multi-view +
overlays land in Phase 3).

Tests: 495 → 497. Manual --tui e2e verified.

Components

File Purpose
src/tui/tui-shim.ts Single import site for @opentui/react — replacing the renderer = swap this one file.
src/tui/components/Header.tsx Run id, status counts, parallel-% gauge (color thresholds per design doc), remote-cache indicator.
src/tui/components/TaskList.tsx One row per task: status glyph + id + elapsed. Selected row highlighted.
src/tui/components/LogPane.tsx Buffered stdout/stderr for the selected/pinned task.
src/tui/components/ProgressBar.tsx ▇▇▇░░ N/M X% parallel strip.
src/tui/components/StatusBar.tsx Bottom keymap hint.
src/tui/App.tsx Composes them; keyboard handler maps j/k, ?, esc, q/Ctrl-C.
src/tui/tui.ts Entry. Creates the renderer, wraps the Phase 2A reducer + Observer adapter, runs a 33ms paint debouncer + 1 Hz sparkline sampler.

CLI

  • --tui / --no-tui parsed in src/cli/run.ts.
  • shouldUseTui() decides whether to mount. Falls back silently
    to the framed-block path when disqualified.
  • Explicit --tui prints vx: TUI unavailable (<reason>) when
    blocked. Reason strings exactly match the test matrix from Phase 2A.
  • Lazy import of src/tui/tui.ts. Non-TUI runs pay zero
    cold-start cost from @opentui/* (~30–60 ms saved per invocation).

Compile-gate (design doc §2)

OpenTUI mounts cleanly under Bun directly — no compile step needed
for the dev/run path. The single-binary bun build --compile story
is deferred until we actually ship a compiled vx; we'll then follow
path (1) sibling-file install from the design doc.

Tooling

  • tsconfig.json gains jsx: react-jsx + jsxImportSource: @opentui/react.
  • react@19 + @types/react@19 added as deps (peer of @opentui/react).
  • All components use React.ReactNode returns (OpenTUI's intrinsics
    return ReactNode, not ReactElement).

Manual e2e

# Inside a project (TTY): TUI mounts (verified locally; CI env can't).
$ vx run build --tui

# CI/non-TTY: silently falls back to framed-block.
$ vx run build --tui   # in this shell (stdin not a TTY)
# → normal framed output

# Disqualifier surfaces under explicit flag:
$ CI=1 vx run build --tui
vx: TUI unavailable (stdout is not a TTY)
# → continues with framed output

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 — 497 pass, 0 fail
  • New: tests/tui-smoke.test.ts (mount + dispatch + dispose),
    tests/cli.test.ts --tui parser cases
  • Manual e2e for --tui, --no-tui, and CI=1 --tui
    disqualifier path

What's NOT in this PR (deferred to Phase 3)

  • Multi-view (1-5 keys for Graph / Workers / Bottlenecks / Queue).
  • Task Detail + Help overlays.
  • Stats sparklines + Cache/RemoteCache + Timeline panels.
  • Filter input (/).
  • Auto-promote default-on (still opt-in for one release cycle).
  • Scheduler cancellation hook (Ctrl-C currently relies on the
    process-level SIGINT trigger).

https://claude.ai/code/session_016HXj6HW6bxSn8EYuKcxTD9


Generated by Claude Code

Ships the minimum-viable interactive TUI promised by Phase 1's design
doc §8. Single screen, framed task list + log pane + progress bar +
keymap hint. Built on top of Phase 2A's pure-function foundation —
zero changes to the reducer/state/selectors.

What's wired:

- `src/tui/tui-shim.ts` — single import site for @opentui/react
  (createCliRenderer, createRoot, useKeyboard, useTerminalDimensions).
  Replacing the renderer = swap this file.
- `src/tui/components/{Header,TaskList,LogPane,ProgressBar,StatusBar}.tsx`
  — props-only React components. Header shows run id, status counts,
  parallel %, remote-cache indicator. TaskList renders one row per
  task with status icon + duration; selected row highlighted.
  LogPane shows the selected/pinned task's buffered output.
  ProgressBar is N/M complete + filled glyph + parallel %.
- `src/tui/App.tsx` — composes the components; keyboard handler maps
  j/k arrows to selectTask, ?/esc to overlay toggles, q/Ctrl-C to
  exit. Single view for Phase 2B (state.activeView=1).
- `src/tui/tui.ts` — entry. Creates the OpenTUI renderer in
  alt-screen mode, wraps the Phase 2A reducer + Observer adapter,
  runs a 33ms paint debouncer + 1 Hz sparkline sampler tick.
  `startTui({ testing: true })` exposes the headless renderer for
  the smoke test.
- `--tui` / `--no-tui` parsed in src/cli/run.ts. shouldUseTui()
  decides whether to mount the renderer; falls back silently to the
  framed-block path when disqualified. Explicit --tui prints
  `vx: TUI unavailable (<reason>)` when blocked. Lazy-loads
  `src/tui/tui.ts` only when use=true so non-TUI runs pay zero
  cold-start cost from @opentui/*.

Tests: 495 → 497.
  - tests/cli.test.ts: --tui / --no-tui parser coverage.
  - tests/tui-smoke.test.ts: mounts OpenTUI in headless mode,
    dispatches runStart+runEnd, disposes idempotently. Catches
    import-time and lifecycle regressions.

Tooling:
  - tsconfig.json gains `jsx: react-jsx` + `jsxImportSource:
    @opentui/react`.
  - react@19 + @types/react@19 added as deps (peer of @opentui/react).

Compile-gate experiment (docs/design/tui-design.md §2): OpenTUI
mounts cleanly under Bun directly (no compile step), so the native
lib is loaded via @opentui/core's own initializer. The
`bun build --compile` single-binary path is deferred — when we ship
a compiled vx, we revisit and follow path (1) sibling-file install
from the design doc.

Manual e2e:
  - `vx run build --tui` in a fixture project falls back silently
    when stdin/stdout isn't a TTY (correct).
  - `CI=1 vx run build --tui` prints
    "vx: TUI unavailable (stdout is not a TTY)" and runs framed
    output.

https://claude.ai/code/session_016HXj6HW6bxSn8EYuKcxTD9
@Exelord
Exelord merged commit 1d079b6 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