forked from imbytecat/fullstack-starter
- 新增根目录 AGENTS.md 作为 monorepo 总览 - 移动 desktop AGENTS.md 从 src-tauri/ 到 apps/desktop/ - 修正 server AGENTS.md 目录结构 (src/server/api/ 而非 src/orpc/) - 明确 desktop 为纯 Tauri 壳子,无前端代码,通过 sidecar 加载 server
4.5 KiB
4.5 KiB
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
# 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
- Start server dev first:
cd ../server && bun dev - Start Tauri:
bun dev(from apps/desktop/) - Tauri connects to localhost:3000 with HMR support
Rust Code Style
Formatting
- Indent: 4 spaces
- Line width: 100 chars
- Run
cargo fmtbefore commit
Naming
| Type | Convention | Example |
|---|---|---|
| Functions/variables | snake_case | find_available_port |
| Types/structs/enums | PascalCase | SidecarProcess |
| Constants | SCREAMING_SNAKE | DEFAULT_PORT |
Imports
// 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
// 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
// Use Tauri's async runtime for spawning
tauri::async_runtime::spawn(async move {
let port = find_available_port(3000).await;
// ...
});
Tauri Patterns
Command Definition
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// Register in Builder
.invoke_handler(tauri::generate_handler![commands::greet])
State Management
struct SidecarProcess(Mutex<Option<CommandChild>>);
// Register state
app.manage(SidecarProcess(Mutex::new(None)));
// Access state
if let Some(state) = app_handle.try_state::<SidecarProcess>() {
*state.0.lock().unwrap() = Some(child);
}
Sidecar Lifecycle
// 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 fmtandcargo clippybefore commit - Use
expect("中文消息")instead ofunwrap() - 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- formattingcargo clippy- lintingcargo check- compilescargo test- tests pass- Tauri app starts and exits cleanly