.claude/skills/tauri-2/tauri-core/tauri-core-architecture/SKILL.md
Use when creating new Tauri 2 apps, understanding project structure, or reasoning about the component model. Prevents mixing Tauri 1.x architecture assumptions with the v2 multi-webview and capability-based model. Covers Rust backend structure, webview layer, IPC bridge model, process model, project layout, and type hierarchy. Keywords: tauri architecture, project structure, IPC bridge, webview layer, process model, Rust backend.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer tauri-core-architectureInstall 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.
| Layer | Technology | Location | Role |
|-------|-----------|----------|------|
| Rust Backend | Rust + Tokio | src-tauri/src/ | Application logic, system access, state management |
| Webview Layer | Platform-native webview | Embedded | Renders HTML/CSS/JS frontend |
| IPC Bridge | JSON-serialized messages | Internal | Connects frontend to backend via invoke() and events |
| Plugin System | Rust crate + npm package | Cargo.toml + package.json | Extends capabilities (fs, dialogs, HTTP, etc.) |
| Permission System | Capabilities + permissions | src-tauri/capabilities/ | Fine-grained access control (replaces v1 allowlist) |
| Platform | Webview Engine | |----------|---------------| | Windows | WebView2 (Chromium-based) | | macOS | WKWebView | | Linux | WebKitGTK | | iOS | WKWebView | | Android | Android WebView |
| Package | Type | Purpose |
|---------|------|---------|
| @tauri-apps/cli | devDependency (npm) | CLI tooling for dev/build |
| @tauri-apps/api | dependency (npm) | Frontend API for invoke, events, paths |
| tauri | dependency (Cargo) | Core Rust framework |
| tauri-build | build-dependency (Cargo) | Build script helpers |
| serde + serde_json | dependency (Cargo) | Serialization for IPC |
NEVER block the main thread with synchronous I/O in commands -- ALWAYS use async for file, network, or long-running operations. Blocking freezes the entire UI.
NEVER call .invoke_handler() more than once on Builder -- only the LAST call takes effect. Put ALL commands in a single generate_handler![] macro.
NEVER mark command functions as pub when defined directly in lib.rs -- the glue code generation prevents it. Move commands to a separate module if they need to be public.
NEVER wrap managed state in Arc -- Tauri wraps state in Arc internally. Using app.manage(Arc::new(data)) adds a redundant layer.
NEVER use &str in async command parameters -- borrowed references do not work with async command spawning. ALWAYS use String instead.
NEVER use State<'_, T> when you registered Mutex<T> -- this causes a runtime panic, not a compile error. ALWAYS match the exact registered type: State<'_, Mutex<T>>.
Tauri 2 uses a two-process architecture:
#[tauri::command] functions as IPC endpointsapp.manage() with Arc-wrapped storageinvoke()The bridge between processes uses JSON-serialized messages:
Frontend Rust Backend
| |
|--- invoke('cmd', {args}) -------->| (JSON request)
| |--- execute command
|<-- Result<T, E> (JSON) ----------| (JSON response)
| |
|--- emit('event', payload) ------->| (event pub/sub)
|<-- emit('event', payload) --------| (event pub/sub)
| |
|<-- Channel.send(data) -----------| (streaming)
invoke() sends a JSON object with camelCase keys; Rust receives snake_case parametersSerialize + Clone payloadstauri::ipc::Responsemy-tauri-app/
├── src/ # Frontend source (React/Vue/Svelte/vanilla)
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── src-tauri/ # Rust backend
│ ├── src/
│ │ ├── lib.rs # Main app logic (mobile entry point)
│ │ └── main.rs # Desktop entry point (calls lib.rs)
│ ├── capabilities/ # Permission capability files (JSON/TOML)
│ │ └── default.json # Default capabilities
│ ├── permissions/ # Custom command permissions (TOML only)
│ ├── icons/ # Application icons (all required sizes)
│ ├── gen/ # Generated files (DO NOT edit manually)
│ │ └── schemas/
│ │ ├── desktop-schema.json
│ │ ├── mobile-schema.json
│ │ └── remote-schema.json
│ ├── Cargo.toml # Rust dependencies
│ ├── Cargo.lock # Deterministic build lock
│ ├── tauri.conf.json # Main configuration
│ ├── build.rs # Cargo build script
│ └── .taurignore # Exclude files from dev watcher
├── package.json # Frontend dependencies
├── tsconfig.json # TypeScript config (if applicable)
└── vite.config.ts # Bundler config (if Vite)
src-tauri/Cargo.lock (deterministic builds)src-tauri/target/ (build artifacts)src-tauri/gen/ (auto-generated schemas)Desktop and mobile share logic through lib.rs:
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![/* commands */])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// src-tauri/src/main.rs (desktop only)
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
app_lib::run();
}
ALWAYS use the lib.rs + main.rs split pattern for Tauri 2 -- this is required for mobile support and is the standard project layout.
| Type | Description | Traits |
|------|------------|--------|
| App | Full application instance (only in setup()) | Manager, Emitter, Listener |
| AppHandle | Lightweight clone-safe handle to the app | Manager, Emitter, Listener, Clone + Send + Sync |
| WebviewWindow | Combined window + webview (most common) | Manager, Emitter, Listener |
| Window | OS-level window (without webview) | Manager, Emitter, Listener |
| Webview | Webview inside a window | Manager, Emitter, Listener |
| State<'_, T> | Managed state accessor in commands | -- |
| Builder | Application configuration builder | -- |
The Manager trait is the unifying interface. It is implemented by App, AppHandle, Webview, WebviewWindow, and Window. Key methods:
| Method | Return Type | Purpose |
|--------|------------|---------|
| app_handle() | &AppHandle<R> | Get the AppHandle |
| config() | &Config | Access tauri.conf.json |
| state::<T>() | State<'_, T> | Access managed state |
| try_state::<T>() | Option<State<'_, T>> | Access state without panic |
| manage(state) | bool | Register new state |
| get_webview_window(label) | Option<WebviewWindow<R>> | Get window by label |
| webview_windows() | HashMap<String, WebviewWindow<R>> | Get all windows |
| get_window(label) | Option<Window<R>> | Get OS window by label |
| path() | &PathResolver<R> | Access path resolver |
| package_info() | &PackageInfo | Get package metadata |
| Method | Purpose |
|--------|---------|
| emit(event, payload) | Broadcast to ALL targets |
| emit_to(target, event, payload) | Emit to a specific target |
| emit_filter(event, payload, filter_fn) | Emit to targets matching a filter |
| emit_str(event, json_string) | Emit with pre-serialized JSON |
| Method | Purpose |
|--------|---------|
| listen(event, handler) | Listen for events (returns EventId) |
| once(event, handler) | Listen once, auto-remove after first event |
| unlisten(id) | Remove a listener |
| listen_any(event, handler) | Listen from any source |
Most types are generic over R: Runtime. With the default wry feature, R resolves to Wry. Use the generic form in plugins or for test mocking:
#[tauri::command]
async fn my_command<R: Runtime>(
app: AppHandle<R>,
window: WebviewWindow<R>,
) -> Result<(), String> {
Ok(())
}
The Builder is the single entry point for configuring a Tauri application:
tauri::Builder::default()
.manage(MyState::default()) // state
.plugin(tauri_plugin_shell::init()) // plugins
.invoke_handler(tauri::generate_handler![cmd1, cmd2]) // commands
.setup(|app| { // init hook
let handle = app.handle().clone();
// app is &mut App -- full access, runs once
Ok(())
})
.on_window_event(|window, event| { /* ... */ }) // window events
.on_menu_event(|app, event| { /* ... */ }) // menu events
.menu(|app| { /* build menu */ }) // app menu
.run(tauri::generate_context!())
.expect("error running app");
| Method | Purpose |
|--------|---------|
| manage(T) | Register managed state |
| plugin(P) | Register a plugin |
| invoke_handler(F) | Register ALL command handlers |
| setup(F) | App initialization hook |
| menu(F) | Set the application menu |
| on_menu_event(F) | Menu event handler |
| on_window_event(F) | Window event handler |
| on_webview_event(F) | Webview event handler |
| on_page_load(F) | Page load handler |
| on_tray_icon_event(F) | Tray icon event handler |
| build(Context) | Build without running (returns App) |
| run(Context) | Build and run the app |
| any_thread(self) | Allow running on any thread |
| API Area | Key Functions | Import Path |
|----------|--------------|-------------|
| Commands | invoke<T>(), Channel<T> | @tauri-apps/api/core |
| Events | listen(), emit(), emitTo(), once() | @tauri-apps/api/event |
| Windows | getCurrentWindow(), getAllWindows() | @tauri-apps/api/window |
| Webviews | getCurrentWebview() | @tauri-apps/api/webview |
| Paths | appDataDir(), join(), BaseDirectory | @tauri-apps/api/path |
| Utilities | convertFileSrc(), isTauri() | @tauri-apps/api/core |
| Testing | mockIPC(), mockWindows(), clearMocks() | @tauri-apps/api/mocks |
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.