| name | tauri-desktop-app |
| description | Scaffolds new Tauri 2 projects with ultra-low-latency patterns, or audits existing Tauri apps for compliance with best practices (non-blocking commands, event-driven architecture, IPC efficiency). Invoke with /tauri-desktop-app new [description] or /tauri-desktop-app audit. |
Tauri 2 Ultra-Low-Latency Desktop App Guide
Overview
Build Tauri 2 desktop applications with near-native responsiveness. This skill enforces ultra-low-latency patterns: non-blocking commands, event-driven architecture, efficient IPC, and Rust state ownership.
Version Policy: Always use the latest stable versions. This skill uses version ranges or @latest tags to ensure you get the most recent compatible releases.
Modes:
new [description] - Scaffold a new Tauri 2 project with best practices
audit - Review existing code for performance anti-patterns
User input: $ARGUMENTS
Instruction: Parse the first word from $ARGUMENTS as the MODE. The rest is CONTEXT.
Responsiveness Targets
| Metric | Target | Critical |
|---|
| Cold start | < 300ms | YES |
| UI interactive | < 100ms | YES |
| IPC latency | < 5ms | YES |
| No UI freeze | > 16ms | YES |
MODE: NEW - Project Scaffolding
The user wants to build: [CONTEXT from $ARGUMENTS after "new"]
Guide the user through creating a Tauri 2 project with ultra-low-latency patterns.
0. Use create-tauri-app CLI (with temp directory workaround)
PROBLEM: create-tauri-app refuses to run in non-empty directories and requires an interactive terminal.
SOLUTION: Create a temp directory, run the CLI there, then merge files into the target directory.
TEMP_DIR=$(mktemp -d)
cd "$TEMP_DIR"
echo "y" | npx --yes create-tauri-app@latest my-app --template react-ts --manager npm || true
npx --yes create-tauri-app@latest my-app --template react-ts --manager npm
cp -r "$TEMP_DIR/my-app/"* "$OLDPWD/"
cp -r "$TEMP_DIR/my-app/".* "$OLDPWD/" 2>/dev/null || true
cd "$OLDPWD"
rm -rf "$TEMP_DIR"
NOTE: Always use @latest tag or omit version to get the latest stable version.
FALLBACK: If the CLI fails, use manual scaffolding below.
1. Manual Scaffolding (use latest versions)
npm init -y
npm install react react-dom
npm install --save-dev vite typescript @vitejs/plugin-react
npm install --save-dev @tauri-apps/cli @tauri-apps/api
mkdir -p src-tauri/src/commands
mkdir -p src-tauri/src/state
mkdir -p src-tauri/src/workers
mkdir -p src-tauri/tests
mkdir -p src/hooks
mkdir -p src/utils
mkdir -p src/components
Create package.json scripts (add to package.json):
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"tauri": "tauri",
"tauri:dev": "tauri dev",
"tauri:build": "tauri build"
}
}
Create vite.config.ts:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
clearScreen: false,
server: {
port: 1420,
strictPort: true,
watch: {
ignored: ["**/src-tauri/**"],
},
},
envPrefix: ["VITE_", "TAURI_"],
build: {
target: ["es2021", "chrome100", "safari13"],
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
sourcemap: !!process.env.TAURI_DEBUG,
},
});
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
Create index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tauri App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Create src/main.tsx:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./styles.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Create src/App.tsx:
import { useState } from "react";
import { invoke } from "@tauri-apps/api/core";
function App() {
const [greeting, setGreeting] = useState("");
const greet = async () => {
setGreeting(await invoke("greet", { name: "World" }));
};
return (
<div className="container">
<h1>Welcome to Tauri!</h1>
<button onClick={greet}>Greet</button>
<p>{greeting}</p>
</div>
);
}
export default App;
Create src/styles.css:
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
}
body {
margin: 0;
min-height: 100vh;
}
.container {
padding: 2rem;
text-align: center;
}
Create src-tauri/Cargo.toml:
[package]
name = "app-name"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
[lib]
name = "app_name_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["sync", "rt-multi-thread", "macros"] }
Create src-tauri/build.rs:
fn main() {
tauri_build::build()
}
Create src-tauri/src/lib.rs:
pub use commands::greet;
mod commands;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![
commands::greet,
])
.setup(|app| {
if let Some(window) = app.get_webview_window("main") {
window.show()?;
}
let app_handle = app.clone();
std::thread::spawn(move || {
let _ = app_handle.emit("app-ready", ());
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Create src-tauri/src/main.rs:
fn main() {
app_name_lib::run()
}
Create src-tauri/src/commands/mod.rs:
use tauri::AppHandle;
#[tauri::command]
pub fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
Create src-tauri/tauri.conf.json:
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "App Name",
"version": "0.1.0",
"identifier": "com.example.app-name",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "App Name",
"width": 800,
"height": 600,
"resizable"
2. Configure Cargo.toml for Maximum Performance
File: src-tauri/Cargo.toml
[profile.release]
lto = "fat"
codegen-units = 1
opt-level = 3
strip = true
panic = "abort"
[profile.dev]
opt-level = 1
3. Create Rust Backend Structure
mkdir -p src-tauri/src/commands
mkdir -p src-tauri/src/state
mkdir -p src-tauri/src/workers
File: src-tauri/src/state/mod.rs
use std::sync::{Arc, Mutex};
#[derive(Default)]
pub struct AppState {
pub is_ready: bool,
pub settings: Option<Settings>,
}
4. Non-Blocking Command Template
File: src-tauri/src/commands/mod.rs
use tauri::AppHandle;
use crate::state::AppState;
#[tauri::command]
pub async fn start_long_operation(app: AppHandle) -> Result<(), String> {
let app_clone = app.clone();
std::thread::spawn(move || {
let _ = app_clone.emit("operation-progress", 50);
let _ = app_clone.emit("operation-complete", "done");
});
Ok(())
}
#[tauri::command]
pub fn blocking_operation(path: String) -> Result<String, String> {
let data = std::fs::read_to_string(path)?;
Ok(data)
}
5. Frontend Hooks Directory
mkdir -p src/hooks
mkdir -p src/utils
Copy templates from the skill hooks folder:
hooks/useTauriCommand.ts - For fast commands (<5ms)
hooks/useTauriEvent.ts - For real-time updates (MANDATORY)
hooks/useTauriLoad.ts - Auto-load with event refresh
hooks/useTauriMutation.ts - For mutations with batch support
utils/toastr.tsx - Error handling
6. Window-First Rendering
File: src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
if let Some(window) = app.get_webview_window("main") {
window.show()?;
}
let app_handle = app.clone();
std::thread::spawn(move || {
app_handle.emit("app-ready", ()).ok();
});
Ok(())
})
.invoke_handler(tauri::generate_handler![
get_status,
start_long_operation,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
7. Implement Core Features
Based on CONTEXT, create:
- Command modules in
src-tauri/src/commands/
- Feature-specific hooks in
src/hooks/
- UI components in
src/components/
Always follow ultra-low-latency patterns:
- Commands return immediately, use
thread::spawn for heavy work
- Emit events for progress/updates (no polling)
- Batch IPC calls
- Keep state in Rust with
Arc<Mutex<T>>
New Project Checklist
[ ] Tauri 2 initialized
[ ] Cargo.toml has release profile with LTO
[ ] src-tauri/src/commands/ created
[ ] src-tauri/src/state/ created with AppState
[ ] src/hooks/ created with useTauriEvent
[ ] lib.rs has window-first rendering
[ ] All commands use thread::spawn for heavy work
[ ] Events emitted for progress (no polling)
[ ] Core features implemented based on user requirements
MODE: AUDIT - Performance Review
Perform a comprehensive review of the existing Tauri app for ultra-low-latency compliance.
Audit Checklist
1. CRITICAL: No Blocking in Commands
Check: src-tauri/src/commands/*.rs
โ Flag these patterns:
std::fs::read_to_string(path)?;
std::thread::sleep(...);
for item in large_list {
heavy_computation(item);
}
โ
Required fix:
std::thread::spawn(move || {
let result = blocking_work();
app.emit("result", result).ok();
});
2. MANDATORY: No Polling in Frontend
Check: src/components/*.tsx, src/hooks/*.ts
โ Flag this pattern:
useEffect(() => {
const interval = setInterval(async () => {
await invoke("get_status");
}, 100);
return () => clearInterval(interval);
}, []);
โ
Required fix:
useEffect(() => {
const unlisten = listen("status-changed", (event) => {
setStatus(event.payload);
});
return () => unlisten.then(fn => fn());
}, []);
3. HIGH: No Per-Item IPC
Check: All .tsx and .ts files
โ Flag this pattern:
for (const item of items) {
await invoke("save_item", { item });
}
โ
Required fix:
await invoke("save_items", { items });
4. HIGH: Window-First Rendering
Check: src-tauri/src/lib.rs
โ Flag this pattern:
fn setup() {
heavy_model_load()?;
window.show()?;
}
โ
Required fix:
fn setup() {
window.show()?;
thread::spawn(|| {
heavy_model_load();
});
}
5. MEDIUM: Build Optimization
Check: src-tauri/Cargo.toml
Required:
[profile.release]
lto = "fat"
codegen-units = 1
strip = true
Audit Report Format
Output the results in this format:
# Tauri Performance Audit Report
## Latency Risk Score: XX/100
### Critical Issues (Must Fix)
1. **Blocking I/O in command** - `src-tauri/src/commands/recording.rs:45`
- Problem: `std::fs::write` blocks the command
- Fix: Spawn thread and emit event when complete
### High Priority
2. **Polling in frontend** - `src/components/Dashboard.tsx:23`
- Problem: `setInterval` polling for status
- Fix: Use `listen("status-changed")` event instead
### Benchmarks
- Cold start: XXXms (target: <300ms)
- IPC latency estimated: Based on command patterns
### Verification
- [ ] All commands return immediately
- [ ] No polling loops
- [ ] IPC batched where applicable
- [ ] Window-first rendering implemented
Quick Reference Patterns
Non-Blocking Command (CRITICAL)
#[tauri::command]
pub async fn process_data(app: AppHandle, input: Data) -> Result<(), String> {
std::thread::spawn(move || {
let result = heavy_processing(input);
app.emit("process-complete", result).ok();
});
Ok(())
}
Event-Driven Frontend (MANDATORY)
setInterval(() => invoke("get_status"), 100);
useEffect(() => {
const unlisten = listen("status-changed", (e) => setStatus(e.payload));
return () => unlisten.then(fn => fn());
}, []);
Rust State (REQUIRED)
app.manage(Arc::new(Mutex::new(AppState::default())));
#[tauri::command]
pub fn get_state(state: State<'_, Arc<Mutex<AppState>>>) -> AppState {
state.lock().unwrap().clone()
}
Batched IPC (HIGH PRIORITY)
for (const item of items) {
await invoke("save_item", { item });
}
await invoke("save_items", { items });
Anti-Patterns (Auto-Fail)
- Blocking UI thread with
std::thread::sleep or long CPU loops
- Polling loops with repeated
invoke("get_status")
- Business logic in JavaScript instead of Rust
- Per-frame IPC calls (>10/sec)