warpdotdev/warp · Archived

tui-ui-guidelines

Guidelines for writing Warp headless TUI (crates/warp_tui) UI code with the cell-grid TuiElement library. Read before any TUI UI work.

Installation

$ npx skills add warpdotdev/warp --skill tui-ui-guidelines

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from warpdotdev/warp · top by installs.

npx skills add warpdotdev/warp

Browse all from warpdotdev/warp

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 64.9K
License LICENSE-AGPL
Default branch master
Open issues 3,847
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,427 B
  • docs SUMMARY.md 159 B

History

  1. First recorded snapshot · 1 installs

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 .app bundle, no GPU/WGSL, and no mouse-pixel model.
  • Element library: crates/warpui_core/src/elements/tui, behind the tui cargo feature. This is a parallel cell-grid element vocabulary, separate from the GUI Element/View library.

Shared with the GUI (do reuse): the Entity/model core in warpcore/warpuiApp/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. app gives shared read access to the core (mirrors the GUI's Element::layout).
  • render(&self, area: TuiRect, buffer: &mut TuiBuffer, ctx: &mut TuiPaintContext) — paint into area of buffer. area's size is what layout returned, clamped to what was available.
  • cursor_position(&self, area, ctx) -> Option<(u16, u16)> — where the terminal cursor should sit within area, 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 returns Box<dyn TuiElement>, mirroring the GUI Element::finish. Always terminate an element with .finish(); never hand-wrap an element in Box::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, and TuiConstrainedBox (e.g. .withmaxcols(N)).
  • Content: TuiText (.withstyle(style), .truncate(), TuiText::fromspans([...])).
  • View/embedding: TuiChildView for embedding another view's rendered element; TuiEventHandler (e.g. .onkey("x", |, , | ...)) to attach handlers to a subtree.
  • Multi-child trait: TuiParentElement provides withchild / withchildren / addchild / addchildren.
  • Geometry (integer cells): TuiSize, TuiRect, TuiConstraint (TuiConstraint::loose(size) / TuiConstraint::tight(size); TuiConstraint::clamp). Also TuiPoint.

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.