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.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guidelines for writing Warp headless TUI (crates/warp_tui) UI code with the cell-grid TuiElement library. Read before any TUI UI work.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Migrates the supported subset of an existing Warp GUI setup into Warp Agent CLI without exposing credentials or application state. Use in Warp Agent CLI when a user wants to copy or move compatible settings or global file-based MCP servers from the desktop app, set up Warp Agent CLI from an existing GUI installation, or understand which Warp data is already shared.
How and when to log (log::* levels, safe_* macros) and report errors to Sentry (report_error!) in the Warp codebase. Use when adding or reviewing any logging or error reporting — picking a log level, deciding log vs. report_error!, keeping sensitive data out of logs, or surfacing an error to Sentry.
Verify a change to Warp's headless TUI front-end (crates/warp_tui) by running it — locally via ./script/run-tui, or in a headless cloud runner via a WARP_API_KEY dogfood build — and reading the rendered screen back (under tmux when it's installed, otherwise directly). Use whenever you change TUI UI, rendering, input, or behavior and need to confirm the real on-screen result.
Add a new feature flag to gate code changes in the Warp codebase.
GUI desktop app only. Create a one-time launch modal in the Warp client (feature announcement, onboarding, etc.). Use when adding a new modal that should appear exactly once per user on startup, gated by a feature flag, with colors sourced from Warp theme tokens and terminal theme colors.
GUI desktop app only. Writes, runs, and debugs Warp integration tests using the custom Builder/TestStep framework in `crates/integration`. Use when adding a new integration test, fixing a failing integration test, wiring a test into the manual runner or nextest suite, or verifying end-to-end UI and terminal behavior in Warp.
| name | tui-ui-guidelines |
| description | Guidelines for writing Warp headless TUI (crates/warp_tui) UI code with the cell-grid TuiElement library. Read before any TUI UI work. |
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.
crates/warp_tui — per-channel console binaries (e.g. crates/warp_tui/src/bin). Run/observe the TUI with ./script/run-tui. There is no .app bundle, no GPU/WGSL, and no mouse-pixel model.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 warp_core/warpui — App/Entity/AppContext/ViewContext, the actions system, Appearance/theming, FeatureFlag runtime checks (FeatureFlag::X.is_enabled() 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/warp_tui/Cargo.toml; the compile-time Cargo-feature bridge in app/Cargo.toml + app/src/lib.rs enabled_features() 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.)
TuiElement traitDefined 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).dispatch_event(&mut self, event, area, event_ctx, 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.Re-exported from crates/warpui_core/src/elements/tui/mod.rs:
TuiFlex (TuiFlex::row() / TuiFlex::column(), with .child(...), .flex_child(...), .with_cross_axis_alignment(...)), TuiContainer, and TuiConstrainedBox (e.g. .with_max_cols(N)).TuiText (.with_style(style), .truncate(), TuiText::from_spans([...])).TuiChildView for embedding another view's rendered element; TuiEventHandler (e.g. .on_key("x", |_, _, _| ...)) to attach handlers to a subtree.TuiParentElement provides with_child / with_children / add_child / add_children.TuiSize, TuiRect, TuiConstraint (TuiConstraint::loose(size) / TuiConstraint::tight(size); TuiConstraint::clamp). Also TuiPoint.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/warp_tui/src/tui_builder.rs) over hardcoding colors — this mirrors the GUI guideline about reusing themes. Construct it per render with TuiUiBuilder::from_app(app), then ask for semantic styles: primary_text_style(), muted_text_style(), dim_text_style(), error_text_style(), success_glyph_style(), accent_border_style(), input_text_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.
Crossterm input is converted (in crate::runtime) to TuiEvent and dispatched through the element tree via dispatch_event; text-cursor placement flows through cursor_position.
Keybindings follow the GUI convention: each TUI view module exposes a top-level init(app) that registers its bindings, aggregated in crates/warp_tui/src/keybindings.rs and called once at TUI startup. Fixed/reserved bindings (e.g. ctrl-c) are tagged with the tui group (TUI_BINDING_GROUP); 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 (register_binding_validators) enforce that any keystroke binding matching a TUI view's context is TUI-owned.
A TuiFlex::column() of styled TuiText children, wrapped in a width cap (illustrative):
let builder = TuiUiBuilder::from_app(app);
let title_style = builder.accent_border_style().add_modifier(Modifier::BOLD);
let muted = builder.muted_text_style();
let column = TuiFlex::column()
.child(
TuiText::new("Warp Agent")
.with_style(title_style)
.truncate()
.finish(),
)
.child(TuiText::new(version).with_style(muted).truncate().finish());
TuiConstrainedBox::new(column.finish())
.with_max_cols(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.
./script/run-tui../script/run-tui) and observing the output in an interactive terminal; the tui-verify-change skill covers this end to end.tui-testing skill.