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
“▶
Structs§
- Live
State - Snapshot the render thread reads on every frame to draw the live
region. Mutated under a
Mutexfrom the cli thread; everything that needs to enter scrollback goes through the history channel instead so the render thread can callinsert_beforefrom 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.
- Build
Kind - History
Item - 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; theSetCurrentStepvariant is the exception — it only mutatesLiveState::current_stepso the inline live region can show a spinner during the next frame. - Step
Status - 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.