codewhale-tui 0.9.2

Terminal UI for open-source and open-weight coding models
# 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 test -p codewhale-tui --test terminal_matrix_qa --locked  # #3758 matrix
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.