# crates/tui — agent guidance Scope: the TUI, the runtime engine embedded in it, and everything a user sees. Read the repo-root `AGENTS.md` first; this file adds the rules that are specific to this crate. ## The shell grammar (do not regress it) The default shell is the underwater system (`src/tui/underwater.rs`, `ocean.rs`, `widgets/`, `views/`). Its contract, in one list: - **One owner per fact.** Route/mode/permission/context live in the header; Tasks/To-do in the top strip; receipts and the single live row in the transcript; phase/cost/detail keys in the footer. Never restate a fact in a second place. - **One live row.** Settled receipts are still; only the active row and the footer phase mark move. Decorative motion exists only in empty idle water and stops the instant the user types or anything needs attention. - **Phase is typed.** `ShellPhase::from_app` derives idle/typing/working/ waiting/approval/done/failed from real app state. Never invent state in a renderer; never compare English strings to detect state (use the enums — the permission chip maps from `ApprovalMode` for exactly this reason). - **Treatment is typed.** `OceanTreatment` (ombre/flat/classic) parses once from settings. Every underwater treatment keeps ambient life; appearance and motion (`low_motion`, `fancy_animations`) are independent axes. - **Footer notices go through the toast system** (`push_status_toast` / `active_status_toast`), never the legacy `status_message` sink directly: toasts carry level + TTL, errors hold sticky, acknowledgements expire. - **Compact tiers shed chrome, not content.** At small sizes a room drops titles/captions/spacers before it drops the object the user opened it to manipulate, and bodies budget from the footer's *wrapped* height (`wrapped_footer_lines` / `action_footer_lines`). - **Rows are objects.** Anything selectable has a hitbox recorded at render time, keyboard + mouse parity, and visible focus. Destructive controls arm before they fire. ## Localization rules - Every user-visible string goes through `tr(locale, MessageId::…)`. No hardcoded English in render paths — the raw-parity tests (`shipped_complete_packs_have_raw_key_parity_with_english`, `message_id_list_english_pack_stay_in_exact_sync`) enforce the key sets, and they exist because the old gate was blinded by the English fallback. - Adding a string = enum variant + `ALL_MESSAGE_IDS` entry + `en.json` key + a translation in every complete pack. See `locales/AGENTS.md`. - Glyphs (`▸ · ▾ ─`), key names (`Enter`, `Alt+?`), and commands (`/fleet setup`) are composed in code, not embedded in translations. ## Verification ```sh cargo test -p codewhale-tui --bins --locked # full unit suite cargo test -p codewhale-tui --test qa_pty --locked # PTY snapshots cargo test -p codewhale-tui --test release_runtime_qa --locked cargo clippy --workspace --all-targets --locked -- -D warnings ``` Run clippy with `--all-targets`: `--bin` alone skips test targets and lets lints reach CI. Real-terminal QA gotchas (learned the hard way): - The local tmux **server** may carry `NO_COLOR=1` and `TERM=dumb` from old VHS runs — launch panes with `env -u NO_COLOR` or all color QA silently lies. tmux also force-enables the low-motion runtime overlay; prove full motion with `TMUX`/`TMUX_PANE` removed. - Scripted PTY input: one Enter on the slash menu both accepts the highlighted match and runs it (#573). A scripted second Enter lands *inside* whatever modal just opened. Send one key, wait, capture. - Judge motion from repeated captures diffed over time, never single screenshots. Layout gates: 40x12, 60x16, 80x24, 100x32, 140x40. - `CODEWHALE_TUI_DEBUG=1` writes per-frame diff sizes to `~/.codewhale/logs/tui-render.log`. Streaming should be tens of cells per frame; a multi-thousand-cell frame is only acceptable on a genuine layout transition. ## Sharp edges - `run_verifiers_background_*` can flake under full-suite parallelism; rerun in isolation before blaming a change. - The workflow *history* card renders with `Locale::En` until locale is threaded through `ToolCell::lines_with_mode` (~30 call sites) — known debt, not a bug to "fix" casually. - The `classic` treatment exists in code but persisted settings normalize it away; do not expand it without a product decision. - See the do-not-delete module list in the repo-root `AGENTS.md` before trusting any dead-code audit.