.claude/skills/tauri-2/tauri-syntax/tauri-syntax-state/SKILL.md
Use when managing application state, sharing data between commands, or debugging state-related panics. Prevents unmanaged state panics, Mutex deadlocks, and unnecessary Arc wrapping around managed state. Covers app.manage(), State<T> injection, AppHandle.state(), thread-safe state with Mutex/RwLock, and initialization patterns. Keywords: tauri state, manage, State<T>, Mutex, RwLock, AppHandle, thread safety, state injection.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer tauri-syntax-stateInstall 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.
| Method | Context | Description |
|--------|---------|-------------|
| Builder::manage(T) | Builder chain | Register state during app construction |
| App::manage(T) | setup() hook | Register state that depends on app initialization |
| AppHandle::manage(T) | Any context | Register state at runtime (rare) |
| Method | Context | Returns |
|--------|---------|---------|
| State<'_, T> | Command parameter | Injected automatically by Tauri |
| app_handle.state::<T>() | Any code with AppHandle | State<'_, T> (panics if not registered) |
| app_handle.try_state::<T>() | Any code with AppHandle | Option<State<'_, T>> (safe) |
| Wrapper | Use Case | Lock Method |
|---------|----------|-------------|
| std::sync::Mutex<T> | Mutable state, no .await while locked | .lock().unwrap() |
| tokio::sync::Mutex<T> | Mutable state with .await while locked | .lock().await |
| std::sync::RwLock<T> | Read-heavy access, concurrent readers | .read().unwrap() / .write().unwrap() |
| None (immutable) | Read-only config data | Direct field access |
NEVER use State<'_, T> when you registered Mutex<T> — the type must match EXACTLY or Tauri panics at runtime, not at compile time. If you registered Mutex<Counter>, the command parameter must be State<'_, Mutex<Counter>>.
NEVER wrap managed state in Arc — Tauri already wraps all managed state in Arc internally. Using app.manage(Arc::new(data)) adds a redundant layer.
NEVER lock the same Mutex twice in the same call chain — this causes a deadlock. If a function holding a lock calls another function that also locks, the thread blocks forever.
NEVER use tokio::sync::Mutex unless you need to hold the lock across .await points — std::sync::Mutex is more efficient for synchronous access.
ALWAYS register state before any command tries to access it — accessing unregistered state via State<T> causes a runtime panic.
ALWAYS match the exact registered type in State<'_, T> — including all wrappers like Mutex, RwLock, etc.
// Tauri 2.x — No Mutex needed for read-only data
struct AppConfig {
api_url: String,
max_retries: u32,
}
tauri::Builder::default()
.manage(AppConfig {
api_url: "https://api.example.com".into(),
max_retries: 3,
})
.invoke_handler(tauri::generate_handler![get_api_url])
#[tauri::command]
fn get_api_url(config: tauri::State<'_, AppConfig>) -> String {
config.api_url.clone()
}
// Tauri 2.x — Standard pattern for mutable state
use std::sync::Mutex;
#[derive(Default)]
struct Counter {
value: u32,
}
tauri::Builder::default()
.manage(Mutex::new(Counter::default()))
.invoke_handler(tauri::generate_handler![increment, get_count])
#[tauri::command]
fn increment(state: tauri::State<'_, Mutex<Counter>>) -> u32 {
let mut counter = state.lock().unwrap();
counter.value += 1;
counter.value
}
#[tauri::command]
fn get_count(state: tauri::State<'_, Mutex<Counter>>) -> u32 {
state.lock().unwrap().value
}
// Tauri 2.x — Use tokio Mutex when holding lock across .await
use tokio::sync::Mutex;
struct Database {
connection: String,
}
#[tauri::command]
async fn save_record(
state: tauri::State<'_, Mutex<Database>>,
data: String,
) -> Result<(), String> {
let db = state.lock().await;
// ... perform async database operations while holding lock ...
Ok(())
}
// Tauri 2.x — Multiple concurrent readers, exclusive writers
use std::sync::RwLock;
struct AppData {
items: Vec<String>,
}
tauri::Builder::default()
.manage(RwLock::new(AppData { items: vec![] }))
#[tauri::command]
fn list_items(state: tauri::State<'_, RwLock<AppData>>) -> Vec<String> {
let data = state.read().unwrap(); // Multiple readers OK
data.items.clone()
}
#[tauri::command]
fn add_item(state: tauri::State<'_, RwLock<AppData>>, item: String) {
let mut data = state.write().unwrap(); // Exclusive access
data.items.push(item);
}
// Tauri 2.x — State that depends on app paths or runtime info
tauri::Builder::default()
.setup(|app| {
let db_path = app.path().app_data_dir()?.join("data.db");
app.manage(Database::new(&db_path)?);
Ok(())
})
// Tauri 2.x — Access state outside of commands
use std::sync::Mutex;
fn background_task(handle: tauri::AppHandle) {
let state = handle.state::<Mutex<Counter>>();
let mut counter = state.lock().unwrap();
counter.value += 1;
}
// Safe variant — returns None if state not registered
fn maybe_access(handle: &tauri::AppHandle) {
if let Some(state) = handle.try_state::<Mutex<Counter>>() {
let counter = state.lock().unwrap();
println!("Count: {}", counter.value);
}
}
The Manager trait provides state() and try_state(). It is implemented by:
| Type | Description |
|------|-------------|
| App | Available in setup() |
| AppHandle | Cloneable, Send + Sync, use in threads |
| Window | Window instance |
| Webview | Webview instance |
| WebviewWindow | Combined window + webview |
All of these types can call .state::<T>() and .try_state::<T>().
Register multiple state types independently:
tauri::Builder::default()
.manage(AppConfig { /* ... */ })
.manage(Mutex::new(UserSession::default()))
.manage(RwLock::new(DocumentStore::default()))
.invoke_handler(tauri::generate_handler![
get_config,
login,
get_document,
])
#[tauri::command]
fn get_config(config: tauri::State<'_, AppConfig>) -> String {
config.api_url.clone()
}
#[tauri::command]
fn login(session: tauri::State<'_, Mutex<UserSession>>, user: String) {
let mut s = session.lock().unwrap();
s.username = user;
}
Each type is registered and accessed independently. There is no limit on the number of state types.
development
Use when integrating Vite with a backend framework, rendering Vite assets from server-side templates, or setting up dev/production HTML serving. Prevents incorrect manifest.json traversal and missing CSS chunk resolution in production. Covers build.manifest configuration, .vite/manifest.json structure, ManifestChunk properties, dev mode HTML setup, production rendering, CSS/JS chunk resolution, and modulepreload polyfill. Keywords: backend integration, manifest.json, ManifestChunk, Django, Laravel, Rails, modulepreload.
development
Use when encountering dev server startup failures, HMR issues, proxy errors, CORS blocks, or module not found errors during development. Prevents misconfiguring server.hmr behind reverse proxies and forgetting appType: 'custom' in middleware mode. Covers HMR full-reload debugging, proxy configuration, CORS setup, HTTPS certificates, server.fs.strict violations, port conflicts, WebSocket failures, file watcher issues, and middleware mode. Keywords: dev server, HMR, proxy, CORS, HTTPS, WebSocket, port conflict, server.fs.strict, middleware mode, file watcher.
development
Use when encountering pre-bundling errors, dependency resolution failures, stale cache issues, or slow development server startup. Prevents excluding CJS dependencies from pre-bundling (which breaks runtime module resolution) and misconfiguring optimizeDeps. Covers CJS/ESM conversion failures, missing dependency auto-discovery, optimizeDeps configuration, monorepo linked dependencies, cache invalidation, browser cache staleness, and large dependency tree performance. Keywords: pre-bundling, optimizeDeps, CJS, ESM, cache, dependency resolution, monorepo, node_modules/.vite.
development
Use when encountering Vite build failures, chunk size warnings, or version-specific build errors. Prevents the common mistake of using deprecated rollupOptions in v8 or misconfiguring build targets and minifiers. Covers Rolldown/Rollup bundling failures, CSS minification errors, sourcemap problems, library mode build failures, BundleError handling, and asset processing errors. Keywords: build error, Rolldown, chunk size, sourcemap, library mode, minify, BundleError, rollupOptions, build.target.