Skip to main content

Module tui

Module tui 

Source
Expand description

Inline-viewport TUI for whisker run.

§Design (vs. the alternate-screen design in #187)

Full-screen ratatui owned the terminal and erased scrollback; cargo / gradle / xcodebuild log bursts during a build no longer fit in any reasonable pane and the user couldn’t scroll back through them. The codex-rs TUI solves this by anchoring a small “live region” at the bottom and pushing everything else into the terminal’s normal scrollback via ANSI scroll-region tricks. We get the same shape for free out of ratatui 0.29’s Viewport::Inline + Terminal::insert_before (with the scrolling-regions feature enabled so we land on the DECSTBM fast path).

Layout while the cli is running:

── terminal scrollback (mouse-wheel scrollable) ──────────────────
  …earlier shell output…
  ▶ Setup
  ✓ Sync gen/ios            124ms
  ▶ Initial build
  warning: unused import: `Foo`   ← captured cargo stderr
  ✓ Initial build           6.2s
  ▶ Install + launch
  …
── live region (LIVE_HEIGHT rows, redraws ~10Hz) ─────────────────
   whisker run · iOS Simulator · rs.example.bar · building · 4.1s
   ⠋ xcodebuild …

   q  quit
──────────────────────────────────────────────────────────────────

§Subprocess output → scrollback

cargo / gradle / xcodebuild and every whisker_build::ui::* call write to stderr. We dup2 STDERR_FILENO to a pipe whose read end a dedicated thread drains line-by-line, strips ANSI escapes from, and sends through an mpsc channel. The render thread drains that channel each frame and calls Terminal::insert_before per line, so captured output lands above the live region — which the terminal’s scrollback keeps for us. ratatui’s backend is wired to the saved original stderr fd so its own draw escapes don’t self-loop into the pipe.

Because stderr is no longer a TTY once we dup2 it, cargo / gradle / xcodebuild automatically fall back to line-based output (no in-place progress bars), which is exactly what we want for scrollback.

§State machine

AppPhase tracks where the dev loop is. The cli calls TuiHandle::set_phase for phases it drives directly (Setup, Initializing); dev-server events drive the rest via TuiHandle::apply_event. Each transition emits a one-line “▶ ” / “✓ Xs” history entry plus updates the live header.

Structs§

LiveState
Snapshot the render thread reads on every frame to draw the live region. Mutated under a Mutex from the cli thread; everything that needs to enter scrollback goes through the history channel instead so the render thread can call insert_before from the thread that owns the ratatui terminal.
Tui
TuiHandle
Cheap-to-clone handle the cli code passes around to update the live region and to commit lines to scrollback. Thread-safe; non-blocking on send (a slow render thread can’t stall the build).

Enums§

AppPhase
Which phase of the dev loop the user is currently watching. Drives the live-region header label + spinner color.
BuildKind
HistoryItem
One message the render thread receives from upstream producers (cli code, dev-server events, and the stderr capture thread). Most variants paint a row into the terminal’s scrollback via Terminal::insert_before; the SetCurrentStep variant is the exception — it only mutates LiveState::current_step so the inline live region can show a spinner during the next frame.
StepStatus
Outcome of a completed step. Pushed to scrollback by TuiHandle::finish_step; never rendered in the live region.

Functions§

apply_event
Apply a dev-server event to the live state and emit any history entries the transition implies. Pure — the test suite exercises it without any terminal io.