cd packages/desktop/src-tauri
cargo check # Quick compilation check
cargo clippy # Linting
cargo build # Build
cargo test # Run tests (full suite ~2min)ALWAYS prefer scoped tests over full suite. The full cargo test takes ~2 minutes. Use module-scoped tests during development:
# Run tests for a specific module (PREFERRED)
cargo test --lib acp::parsers::adapters # adapter tests only
cargo test --lib acp::parsers::tests::cursor # cursor parser tests only
cargo test --lib acp::session_update # session update tests only
# Run a single test by name
cargo test --lib test_name_here
# Run full suite only before commit/PR (or when changes span many modules)
cargo test --libWhen to use scoped vs full:
- Scoped (default): When changes are in 1-3 modules. Run tests for those modules only.
- Full suite: Before committing, after cross-cutting refactors, or when unsure about blast radius.
For the common local loop in this repo, prefer bun run test:rust:fast from packages/desktop before reaching for broader Rust runs. It now maps to cargo test --lib -- --skip claude_history::export_types, so you get full lib coverage without compiling the opt-in manual benchmark and live integration targets.
Manual benchmark and live integration targets now require the manual-test-targets feature. Run them explicitly when needed:
cd packages/desktop/src-tauri
cargo test --features manual-test-targets --test startup_scan_benchmark
cargo test --features manual-test-targets --test indexer_benchmark
cargo test --features manual-test-targets --test codex_scanner_benchmark
cargo test --features manual-test-targets --test codex_scanner_test
cargo test --features manual-test-targets --test voice_transcription- Run
cargo checkorcargo clippybefore considering code complete - Fix all compilation errors before submitting changes
- Address warnings when possible
- Use Rust best practices and follow existing code style
- Add appropriate error handling with
anyhow::Context
- Define command function in appropriate module (e.g.,
src-tauri/src/acp/commands.rs) - Mark with
#[tauri::command]and#[specta::specta]attributes - Return
Result<T, String>whereTis the success type - Ensure all types used in command parameters and return values have
#[derive(specta::Type)] - Register command in
src-tauri/src/lib.rsin the.invoke_handler()chain - Add command to
src-tauri/src/commands/mod.rsin thecollect_commands![]macro - Use the type-safe client from frontend:
tauriClient.acp.commandName(args) - Regenerate TypeScript bindings:
cargo test export_command_bindings -- --nocapture
This project uses specta with tauri-specta for automatic TypeScript type generation from Rust types.
- Add
#[derive(specta::Type)]to your Rust struct or enum:
#[derive(Debug, Clone, Serialize, Deserialize, specta::Type)]
pub struct MyNewType {
pub field: String,
pub count: i32,
}- Add the type to the export in
src/claude_history/export_types.rs:
export_type!(MyNewType);- Regenerate TypeScript types:
cargo test export_types -- --nocapture- Import the generated types in TypeScript:
import type { MyNewType } from "../services/converted-session-types.js";src/lib/services/claude-history-types.ts- HistoryEntry typessrc/lib/services/converted-session-types.ts- Session, tool, and message typessrc/lib/services/command-names.ts- Tauri command name constants
- Use
specta::Typederive macro for types that need TypeScript generation - For
i64fields, specta exports them asnumber(configured viaBigIntExportBehavior::Number) - The JsonValue type is manually added to generated files for
serde_json::Valuecompatibility
- Service Pattern:
AcpServicemanages resource lifecycle (spawn/stop subprocess) - Command Handlers: Mark functions with
#[tauri::command]and register inlib.rs - Async Rust: All Tauri commands are async and return
Result<T, String> - Error Context: Use
anyhow::Contextto add context to errors before converting to strings - Subprocess Management: Use
tokio::process::Commandfor async subprocess spawning