SKILL.md
tui-ui-guidelines
Guidelines for writing UI code in Warp's headless TUI front-end. This is the TUI counterpart to gui-ui-guidelines (which covers the pixel-based GUI desktop app). Read this once at the start of any TUI UI task, then keep it in mind while implementing.
The TUI is a distinct front-end from the GUI desktop app. Do not carry over GUI assumptions (pixels, mouse-pixel hit-testing, GPU/WGSL, .app bundles, design-system button pixel themes, launch modals). If a GUI guideline is about pixel layout or GPU rendering, it does not apply here.
Where TUI UI code lives
- Front-end views/screens:
crates/warptui— per-channel console binaries (e.g.crates/warptui/src/bin). Run/observe the TUI with./script/run-tui. There is no.appbundle, no GPU/WGSL, and no mouse-pixel model. - Element library:
crates/warpui_core/src/elements/tui, behind thetuicargo feature. This is a parallel cell-grid element vocabulary, separate from the GUIElement/Viewlibrary.
Shared with the GUI (do reuse): the Entity/model core in warpcore/warpui — App/Entity/AppContext/ViewContext, the actions system, Appearance/theming, FeatureFlag runtime checks (FeatureFlag::X.isenabled() works in both front-ends), telemetry, and logging.
Different from the GUI (do NOT use here): the GUI Element/View types, pixel geometry, and GPU/WGSL rendering or pixel-drawn button themes. The TUI has its own crates/warptui/Cargo.toml; the compile-time Cargo-feature bridge in app/Cargo.toml + app/src/lib.rs enabledfeatures() is GUI-app-specific and does not gate TUI code. (The TUI does have hover/click: TuiHoverable and tui_collapsible reuse the shared MouseStateHandle, so own that handle outside render just like the GUI — only pixel-based hit-testing is GUI-only.)
The TuiElement trait
Defined in crates/warpui_core/src/elements/tui/mod.rs. An element measures itself, then paints into a sub-rectangle of a cell buffer:
layout(&mut self, constraint: TuiConstraint, ctx: &mut TuiLayoutContext, app: &AppContext) -> TuiSize— measure against a constraint and return a size within it.appgives shared read access to the core (mirrors the GUI'sElement::layout).render(&self, area: TuiRect, buffer: &mut TuiBuffer, ctx: &mut TuiPaintContext)— paint intoareaofbuffer.area's size is whatlayoutreturned, clamped to what was available.cursor_position(&self, area, ctx) -> Option<(u16, u16)>— where the terminal cursor should sit withinarea, if this element owns it (default:None).present(&mut self, ctx)— participate in the child-view recursion so the presenter records parent/child view relationships (default: nothing; only container/child-view elements override it).dispatchevent(&mut self, event, area, eventctx, ctx, app) -> bool— offer an event to this element, returning whether it was handled (default:false)..finish()— boxing convenience that returnsBox<dyn TuiElement>, mirroring the GUIElement::finish. Always terminate an element with.finish(); never hand-wrap an element inBox::new. It's what the child-taking APIs (TuiFlex::child/with_child,TuiChildView, etc.) expect, and it keeps element trees consistent and readable.
Composition vocabulary
Re-exported from crates/warpui_core/src/elements/tui/mod.rs:
- Layout containers:
TuiFlex(TuiFlex::row()/TuiFlex::column(), with.child(...),.flexchild(...),.withcrossaxisalignment(...)),TuiContainer, andTuiConstrainedBox(e.g..withmaxcols(N)). - Content:
TuiText(.withstyle(style),.truncate(),TuiText::fromspans([...])). - View/embedding:
TuiChildViewfor embedding another view's rendered element;TuiEventHandler(e.g..onkey("x", |, , | ...)) to attach handlers to a subtree. - Multi-child trait:
TuiParentElementprovideswithchild/withchildren/addchild/addchildren. - Geometry (integer cells):
TuiSize,TuiRect,TuiConstraint(TuiConstraint::loose(size)/TuiConstraint::tight(size);TuiConstraint::clamp). AlsoTuiPoint.
Styling
Styles are TuiStyle values (Color, Modifier — e.g. Modifier::BOLD, Modifier::DIM) painted into a TuiBuffer of Cells. Terminal cells have no alpha, so styles are solid.
Prefer the semantic style helpers on TuiUiBuilder (crates/warptui/src/tuibuilder.rs) over hardcoding colors — this mirrors the GUI guideline about reusing themes. Construct it per render with TuiUiBuilder::fromapp(app), then ask for semantic styles: primarytextstyle(), mutedtextstyle(), dimtextstyle(), errortextstyle(), successglyphstyle(), accentborderstyle(), inputtext_style(), etc. The builder owns the theme→style recipes so views ask for "primary text" / "muted text" instead of deriving colors from the theme by hand. Do not reach for raw ANSI slots (e.g. Color::White) directly — those are tuned for dark backgrounds and wash out on light themes.
Events and keybindings
Crossterm input is converted (in crate::runtime) to TuiEvent and dispatched through the element tree via dispatchevent; text-cursor placement flows through cursorposition.
Keybindings follow the GUI convention: each TUI view module exposes a top-level init(app) that registers its bindings, aggregated in crates/warptui/src/keybindings.rs and called once at TUI startup. Fixed/reserved bindings (e.g. ctrl-c) are tagged with the tui group (TUIBINDINGGROUP); editable, user-remappable bindings are named with a tui: prefix. GUI bindings never fire in the TUI — predicate-scoped bindings never match TUI keymap contexts, and predicate-less ones dispatch action types no TUI view handles — and debug-time validators (registerbinding_validators) enforce that any keystroke binding matching a TUI view's context is TUI-owned.
Example: composing a small element tree
A TuiFlex::column() of styled TuiText children, wrapped in a width cap (illustrative):
```rust path=null start=null let builder = TuiUiBuilder::fromapp(app); let titlestyle = builder.accentborderstyle().addmodifier(Modifier::BOLD); let muted = builder.mutedtext_style();
let column = TuiFlex::column() .child( TuiText::new("Warp Agent") .withstyle(titlestyle) .truncate() .finish(), ) .child(TuiText::new(version).with_style(muted).truncate().finish());
TuiConstrainedBox::new(column.finish()) .withmaxcols(48) .finish()
Verify API names against the element library (`crates/warpui_core/src/elements/tui/mod.rs`) and `TuiUiBuilder` (`crates/warp_tui/src/tui_builder.rs`); don't invent methods. Don't treat existing `crates/warp_tui` view code as canonical examples — much of it is early prototyping and isn't the pattern to copy going forward.
## Reference
- Run/observe the TUI with `./script/run-tui`.
- Verify a TUI change by building and running it (`./script/run-tui`) and observing the output in an interactive terminal; the `tui-verify-change` skill covers this end to end.
- Write and run TUI tests with the `tui-testing` skill.