Adds a status line indicating which mode the user is in. Adds a "normal" mode with keyboard shortcuts (including a chorded shortcut 'gg'). Adds a command mode with several basic commands that can be entered into an overlay. Chores: - Cleans up design/claude/plan.md to avoid confusing claude. - Adds some TODOs based on claude feedback.` Reviewed-on: #2 Co-authored-by: Drew Galbraith <drew@tiramisu.one> Co-committed-by: Drew Galbraith <drew@tiramisu.one>
4.2 KiB
CLAUDE.md
Rust TUI coding agent. Ratatui + Crossterm + Tokio. See DESIGN.md for architecture decisions and PLAN.md for implementation phases.
Commands
cargo build: Build the projectcargo test: Run all unit and integration testscargo test --lib: Unit tests onlycargo test --test '*': Integration tests onlycargo clippy -- -D warnings: Lint (must pass with zero warnings)cargo fmt --check: Format checkcargo run -- --project-dir <path>: Run against a project directory
Architecture
Six modules with strict boundaries:
src/app/-- Wiring, lifecycle, tokio runtime setupsrc/tui/-- Ratatui rendering, input handling, vim modes. Communicates with core ONLY via channels (UserAction-> core,UIEvent<- core). Never touches conversation state directly.src/core/-- Conversation tree, orchestrator loop, sub-agent lifecyclesrc/provider/--ModelProvidertrait + Claude implementation. Leaf module, no internal dependencies.src/tools/--Tooltrait, registry, built-in tools. Depends only onsandbox.src/sandbox/-- Landlock policy, path validation, command execution. Leaf module.src/session/-- JSONL logging, session read/write. Leaf module.
The channel boundary between tui and core is critical -- never bypass it. The TUI is a frontend; core is the engine. This separation enables headless mode for benchmarking.
Code Style
- Use
thiserrorfor error types, notanyhowin library code (anyhowonly inmain.rs/app) - Prefer
impl Traitreturn types over boxing when possible - All public types need doc comments
- No
unwrap()in non-test code -- use?or explicit error handling - Async functions should be cancel-safe where possible
- Use
tracingfor structured logging, notprintln!orlog
Documentation
Prefer a literate style: doc comments should explain why and how, not just restate the signature.
Use only characters available on a standard US QWERTY keyboard in all doc comments and inline comments. Specifically:
- Use
->and<-instead of Unicode arrow glyphs - Use
--instead of em dashes or en dashes - Use
+,-,|for ASCII box diagrams instead of Unicode box-drawing characters - Use
...instead of the ellipsis character - Spell out "Section N.N" instead of the section-sign glyph
When a function or type implements an external protocol or spec:
- Document the relevant portion of the protocol inline (packet shapes, event sequences, state machines)
- Link to the authoritative external source -- API reference, RFC, WHATWG spec, etc.
- Include a mapping table or lifecycle diagram when there are multiple cases to distinguish
For example, run_stream in src/provider/claude.rs documents the full SSE event sequence in a text diagram and links to both the Anthropic streaming reference and the WHATWG SSE spec. Aim for that level of context in any code that speaks a wire format or external API.
Conversation Data Model
Events use parent IDs forming a tree (not a flat list). This enables future branching. Every event has: id, parent_id, timestamp, event_type, token_usage. A "turn" is all events between two user messages -- this is the unit for token tracking.
Testing
- Unit tests go in the same file as the code (
#[cfg(test)] mod tests) - Integration tests go in
tests/ - TUI widget tests use
ratatui::backend::TestBackend - Provider tests replay recorded SSE fixtures from
tests/fixtures/ - Sandbox tests use
tempdirand skip Landlock-specific assertions if kernel < 5.13 - Run
cargo testbefore every commit
Key Constraints
- All file I/O and process spawning in tools MUST go through
Sandbox-- never usestd::fsorstd::process::Commanddirectly in tool implementations - The
ModelProvidertrait must remain provider-agnostic -- no Claude-specific types in the trait interface - Session JSONL is append-only. Never rewrite history. Branching works by writing new events with different parent IDs.
- Token usage must be tracked per-event and aggregatable per-turn
Do Not
- Add MCP support (deferred, but keep tool trait compatible)
- Use
unsafewithout discussion - Add dependencies without checking if an existing dep covers the use case
- Modify test fixtures without re-recording from a real API session