# AGENTS.md - Desktop App Guidelines Tauri v2 desktop shell - a lightweight wrapper that loads the server app via sidecar. ## Architecture - **Type**: Tauri v2 desktop application (shell only) - **Design**: Tauri provides native desktop APIs; all web logic handled by sidecar - **Sidecar**: The compiled server binary runs as a child process - **Dev mode**: Connects to `localhost:3000` (requires server dev running) - **Prod mode**: Automatically starts sidecar binary **This app has NO frontend src** - it loads the server app entirely. ## Commands ```bash # Development (from apps/desktop/) bun dev # Copy sidecar + start Tauri dev # Build bun build # Copy sidecar + build Tauri installer # Rust Commands (from src-tauri/) cargo check # Compile check cargo clippy # Linter cargo fmt # Formatter cargo test # Run tests cargo test test_name -- --nocapture # Single test with output ``` ## Directory Structure ``` apps/desktop/ ├── src-tauri/ # Rust Tauri code │ ├── src/ │ │ ├── main.rs # Entry point (calls lib::run) │ │ ├── lib.rs # Core app logic (plugins, commands, state) │ │ ├── commands/ │ │ │ └── mod.rs # Native desktop commands │ │ └── sidecar.rs # Sidecar process management │ ├── binaries/ # Sidecar binaries (copied from server build) │ ├── capabilities/ # Tauri v2 permission config │ ├── icons/ # App icons │ ├── Cargo.toml # Rust dependencies │ └── tauri.conf.json # Tauri configuration ├── copy.ts # Script to copy server binary to binaries/ ├── package.json └── tsconfig.json ``` ## Development Workflow 1. **Start server dev first**: `cd ../server && bun dev` 2. **Start Tauri**: `bun dev` (from apps/desktop/) 3. Tauri connects to localhost:3000 with HMR support ## Rust Code Style ### Formatting - **Indent**: 4 spaces - **Line width**: 100 chars - Run `cargo fmt` before commit ### Naming | Type | Convention | Example | |------|------------|---------| | Functions/variables | snake_case | `find_available_port` | | Types/structs/enums | PascalCase | `SidecarProcess` | | Constants | SCREAMING_SNAKE | `DEFAULT_PORT` | ### Imports ```rust // Order: std → external crates → internal modules (separated by blank lines) use std::sync::Mutex; use tauri::Manager; use tauri_plugin_shell::ShellExt; use crate::sidecar::SidecarProcess; ``` ### Error Handling ```rust // Use expect() with Chinese error messages let sidecar = app_handle .shell() .sidecar("server") .expect("无法找到 server sidecar"); // Log with emoji for clear feedback println!("✓ Sidecar 启动成功!"); eprintln!("✗ Sidecar 启动失败"); ``` ### Async Code ```rust // Use Tauri's async runtime for spawning tauri::async_runtime::spawn(async move { let port = find_available_port(3000).await; // ... }); ``` ## Tauri Patterns ### Command Definition ```rust #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}!", name) } // Register in Builder .invoke_handler(tauri::generate_handler![commands::greet]) ``` ### State Management ```rust struct SidecarProcess(Mutex>); // Register state app.manage(SidecarProcess(Mutex::new(None))); // Access state if let Some(state) = app_handle.try_state::() { *state.0.lock().unwrap() = Some(child); } ``` ### Sidecar Lifecycle ```rust // Start sidecar with environment let sidecar = app_handle .shell() .sidecar("server") .expect("无法找到 server sidecar") .env("PORT", port.to_string()); // Cleanup on exit match event { tauri::RunEvent::ExitRequested { .. } | tauri::RunEvent::Exit => { if let Some(child) = process.take() { let _ = child.kill(); } } _ => {} } ``` ## Critical Rules **DO:** - Run `cargo fmt` and `cargo clippy` before commit - Use `expect("中文消息")` instead of `unwrap()` - Always cleanup sidecar on app exit - Declare sidecar in `tauri.conf.json` → `bundle.externalBin` **DON'T:** - Edit `gen/schemas/` (auto-generated) - Use `unwrap()` in production code without context - Block the async runtime (use `spawn_blocking`) ## Pre-commit Checklist - [ ] `cargo fmt` - formatting - [ ] `cargo clippy` - linting - [ ] `cargo check` - compiles - [ ] `cargo test` - tests pass - [ ] Tauri app starts and exits cleanly