d68f003000
CI / Change detection (push) Has been cancelled
CI / Version drift (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Workflow RLM cache (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / npm wrapper smoke (push) Has been cancelled
CI / Mobile runtime smoke (push) Has been cancelled
CI / Workflow lint (push) Has been cancelled
CI / Documentation (push) Has been cancelled
Web Frontend / Lint & Type Check (push) Failing after 1s
Auto-close harvested PRs / close (push) Failing after 1s
cargo-deny / cargo-deny (bans licenses sources) (push) Failing after 1s
Security audit / cargo-audit (push) Failing after 1s
Sync to CNB / sync (push) Failing after 1s
cargo-deny / cargo-deny (advisories) (push) Failing after 3s
Web Frontend / Deploy to Cloudflare (push) Has been cancelled
4.4 KiB
4.4 KiB
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_appderives 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 fromApprovalModefor 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 legacystatus_messagesink 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_IDSentry +en.jsonkey- a translation in every complete pack. See
locales/AGENTS.md.
- a translation in every complete pack. See
- Glyphs (
▸ · ▾ ─), key names (Enter,Alt+?), and commands (/fleet setup) are composed in code, not embedded in translations.
Verification
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=1andTERM=dumbfrom old VHS runs — launch panes withenv -u NO_COLORor all color QA silently lies. tmux also force-enables the low-motion runtime overlay; prove full motion withTMUX/TMUX_PANEremoved. - 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=1writes 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::Enuntil locale is threaded throughToolCell::lines_with_mode(~30 call sites) — known debt, not a bug to "fix" casually. - The
classictreatment 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.mdbefore trusting any dead-code audit.