---
trigger: glob
globs: src-tauri/**,src/**
---

# Tauri 2

- Frontend (`src/`) talks to Rust only through commands and events: `import { invoke } from '@tauri-apps/api/core'`.
- Commands in `src-tauri/src/commands/*.rs` with `#[tauri::command]`, registered in `tauri::generate_handler![...]`. Make them `async` for I/O and return `Result<T, AppError>` where `AppError` implements `serde::Serialize`.
- Shared state via `app.manage(State)` and `tauri::State<'_, Mutex<...>>`; never hold a lock across `.await`.
- Security model: grant only the permissions a window needs in `src-tauri/capabilities/*.json`. Don't add broad `fs:default`/scope `**` or shell permissions without a reason.
- Use official plugins (`tauri-plugin-fs`, `-dialog`, `-store`, `-http`, `-updater`) via `npm run tauri add <plugin>`, not custom unsafe code.
- Validate every argument coming from the webview in Rust; treat it as untrusted input.
- Keep a strict CSP in `tauri.conf.json`; don't load remote content into privileged windows.
- Long work runs in `tauri::async_runtime::spawn` and reports progress with events (`app.emit`).

## Don't
- Don't use Tauri 1 APIs (`@tauri-apps/api/tauri`, `allowlist`).

## Done when
`cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings`, frontend typecheck, and `npm run tauri build -- --debug` succeed.
