skills/rust-tauri-development/SKILL.md
Expert Tauri v2 developer for building desktop apps with Rust backend and web frontend. Activate on: Tauri app, Tauri v2, Rust desktop app, IPC commands, tauri::command, tauri.conf.json, Tauri plugin, WebviewWindow, system tray Tauri, Tauri multi-window. NOT for: Electron apps (use cross-platform-desktop), code signing/distribution (use rust-app-distribution), pure Rust CLI tools (use rust-expert).
npx skillsauth add curiositech/windags-skills rust-tauri-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert Tauri v2 development for building desktop applications with Rust backend and web frontend.
Data Exchange Pattern:
├─ Request/Response needed?
│ ├─ YES: Use #[tauri::command]
│ │ ├─ Sync operation? → fn command() -> Result<T, String>
│ │ └─ I/O operation? → async fn command() -> Result<T, String>
│ └─ NO: Fire-and-forget?
│ ├─ YES: Use events (emit/listen)
│ └─ Large binary data? → Use raw payload channels
State Management Pattern:
├─ Simple shared data?
│ ├─ Read-only → Arc<T>
│ ├─ Mutable + sync access → Arc<Mutex<T>>
│ └─ Mutable + async access → Arc<RwLock<T>>
├─ Database needed?
│ ├─ Simple KV → tauri-plugin-store
│ └─ Relational → tauri-plugin-sql
└─ Cross-process state? → Database or file-based
Window Count Decision:
├─ Single window app?
│ └─ Use default main window only
├─ Settings/preferences needed?
│ └─ Create secondary window with restricted capabilities
├─ Background processing?
│ ├─ System tray → TrayIconBuilder + hidden main window
│ └─ No tray → Keep main window, emit progress events
└─ Multi-document interface?
└─ Create window per document with shared state
Functionality needed:
├─ File system access?
│ ├─ Basic read/write → cargo tauri add fs
│ └─ Complex file ops → Custom commands + std::fs
├─ HTTP requests?
│ ├─ Simple → reqwest in custom commands
│ └─ Complex proxy/auth → Custom plugin
├─ Database?
│ ├─ SQLite/MySQL → cargo tauri add sql
│ └─ Custom storage → Custom plugin
└─ Platform integration?
├─ Notifications → cargo tauri add notification
└─ Custom system APIs → Custom plugin
Detection Rule: If UI freezes during Rust command execution Symptoms:
// BAD: Blocks the main thread
#[tauri::command]
fn heavy_computation() -> String {
std::thread::sleep(Duration::from_secs(5)); // UI freezes
"done".to_string()
}
// GOOD: Async command
#[tauri::command]
async fn heavy_computation() -> String {
tokio::time::sleep(Duration::from_secs(5)).await;
"done".to_string()
}
Detection Rule: If app crashes with "failed to serialize" during invoke() Symptoms:
// BAD: Missing derives
struct MyData {
field: String,
}
// GOOD: Proper derives
#[derive(Serialize, Deserialize)]
struct MyData {
field: String,
}
Detection Rule: If commands fail with "not allowed" or capability errors Symptoms:
{
"permissions": [
"fs:allow-read",
"fs:scope-app-data" // Add specific scopes
]
}
Detection Rule: If shared state shows inconsistent values between windows Symptoms:
// BAD: No synchronization
static mut COUNTER: i32 = 0;
// GOOD: Proper state management
struct AppState {
counter: Arc<Mutex<i32>>,
}
Detection Rule: If layout/behavior differs dramatically between macOS and Windows Symptoms:
Scenario: Create a desktop file manager with folder tree, file operations, and progress tracking.
Step 1: Architecture Decision
Step 2: Setup Capabilities
{
"permissions": [
"core:default",
"fs:allow-read",
"fs:allow-write",
"fs:allow-create",
"fs:scope-downloads",
"fs:scope-documents"
]
}
Step 3: Implement Backend Commands
#[tauri::command]
async fn list_directory(path: String, app: AppHandle) -> Result<Vec<FileEntry>, String> {
let entries = std::fs::read_dir(&path)
.map_err(|e| e.to_string())?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| e.to_string())?;
let mut files = Vec::new();
for (i, entry) in entries.iter().enumerate() {
// Emit progress for large directories
if i % 100 == 0 {
app.emit("scan_progress", i).unwrap();
}
files.push(FileEntry::from_dir_entry(entry)?);
}
app.emit("scan_complete", files.len()).unwrap();
Ok(files)
}
#[tauri::command]
async fn copy_file(src: String, dest: String, app: AppHandle) -> Result<(), String> {
let src_path = Path::new(&src);
let dest_path = Path::new(&dest);
// Use async file operations for large files
let mut src_file = tokio::fs::File::open(&src_path).await
.map_err(|e| e.to_string())?;
let mut dest_file = tokio::fs::File::create(&dest_path).await
.map_err(|e| e.to_string())?;
// Stream copy with progress
let file_size = src_file.metadata().await
.map_err(|e| e.to_string())?.len();
let mut copied = 0u64;
while copied < file_size {
let chunk_size = tokio::io::copy(&mut src_file, &mut dest_file).await
.map_err(|e| e.to_string())?;
copied += chunk_size;
app.emit("copy_progress", (copied * 100) / file_size).unwrap();
}
Ok(())
}
What a novice would miss: Using blocking std::fs operations (freezes UI), forgetting progress events, not handling permission errors.
What an expert catches: Async file operations, progress tracking, proper error propagation, scoped capabilities.
cargo tauri icon for all required sizescargo tauri build)Do NOT use for:
Delegate to:
tools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.