Use when building, migrating (v1→v2), or debugging a Tauri 2.x desktop app — adding a `#[tauri::command]`, fixing "command not allowed"/capabilities/ACL errors, wiring plugins, bundling per-OS, configuring the auto-updater, or attaching a heavy native/Python sidecar. Produces a verified Rust+webview app whose IPC round-trip and bundle target are exercised. Grounded in official Tauri v2 docs (v2.tauri.app); extends with opinionated patterns from 8syncdev's open-musik/zus/sidex and omp-native tooling (codegraph, codebase-memory, serena, engine_*, browser).
Resources
1Install
npx skillscat add 8-sync-dev/8sync/tauri-v2 Install via the SkillsCat registry.
tauri-v2 — build & verify Tauri 2 desktop apps
Canonical base: `references/base.md` (Tauri 2.11.5 /tauri-build 2.6.3, v1→v2 deltas, capabilities/ACL, IPC, plugins, bundling,
updater, sidecar, webview debugging). Opinionated extension:
`references/patterns.md` (11 patterns mined, citedrepo:path, from 8syncdev/open-musik, 8syncdev/zus, 8syncdev/sidex).
When to use
- "add a Tauri command", "call Rust from the frontend", "events aren't reaching
the webview", "invokereturns command not found / not allowed". - Migrating a v1 app, or a capabilities/ACL permission error after a plugin add.
- Wiring a plugin, bundling for an OS, setting up the auto-updater + signing.
- Attaching a heavy native/Python/Go engine as a sidecar or external binary.
- "the webview is blank/black on Linux", CSP violations, asset-protocol paths.
When NOT to use
- Mobile (iOS/Android) targets unless the project already targets them — base §1
notes the lib/main split, but mobile toolchains are out of scope here. - Maintaining a v1-only app with no migration intent.
- Choosing or rewriting the frontend framework — Tauri is framework-agnostic.
- Electron/CEF or pure-browser PWA work (no Tauri involved).
Procedure
Every step names a real tool. Prefer omp primitives over reinvention — when omp
upgrades a primitive, a step that only references it inherits the upgrade.
0. Ground the change before editing
- If
.codegraph/exists:codegraph query "<command_or_fn>"andcodegraph context "tauri ipc"to map commands/events/state. Elsecodegraph init -i .once. After edits:codegraph sync. - If cbm/serena are enabled (opt-in:
8sync harness cbm|serena on), cross-check structure withmcp__codebase_memory_mcp__search_graph/trace_path(calls) if the project is indexed, andmcp__serena__get_symbols_overviewon thesrc-tauri/src/*.rsfile you'll
touch;mcp__serena__get_diagnostics_for_fileto see type errors live. - Read the exact slices you need with
read(src-tauri/tauri.conf.json,src-tauri/Cargo.toml,capabilities/*.json) — never whole-file a >200-line
Rust file blind;codegraph/serena locate first,readthe narrow range.
1. Scaffolding / migration checks
- New app:
cargo tauri init(or the create-tauri-app template). Confirm"$schema": "https://schema.tauri.app/config/2". - v1→v2: run
pnpm tauri migrate(orcargo tauri migrate), then hand-verify
the breaking list in base §1 — allowlist→capabilities,tauri→appkey,distDir→frontendDist, plugins extraction,createUpdaterArtifacts,
Windows origin scheme,Window→WebviewWindow. Usegrepon the old config
forallowlist/tauri.updater/distDirto find every site to fix.
2. Capabilities / ACL (the #1 error source)
- "command/plugin not allowed" → the runtime error names the missing permission
verbatim. Open the capability file bound to the emitting window
(capabilities/default.json), append that identifier topermissions. Prefer
granularplugin:allow-<cmd>overplugin:default. See patterns P5/P11. - The authoritative identifier list is the build-generated
src-tauri/gen/schemas/desktop-schema.json—readit when unsure of the
exact string. Add a capability file per window when windows diverge.
3. IPC — commands, state, events
- Define
#[tauri::command]with flat args returningResult<T, String>
(patterns P2). InjectAppHandle/State<'_, T>by type. - Register every command in
.invoke_handler(tauri::generate_handler![mod::cmd, …])— a missing entry is
"command not found". - Manage state in
.setup(|app| { app.manage(your_state); Ok(()) }); in async
commands, clone owned data out ofStatebefore the.await(P4). - Push progress with
app.emit("name", &payload); frontendlisten("name", …). Pick events overipc::Channelwhen state must survive a
webview reload (P4/P8). - Frontend: wrap
invoke/listen/convertFileSrcfrom@tauri-apps/api/core+/eventbehind aninTauriguard (P8).
4. Plugins
- Add
tauri-plugin-<name> = "2"(Cargo) +@tauri-apps/plugin-<name>(JS),
register.plugin(tauri_plugin_name::init()), grant permission (step 2). - Official plugins:
tauri-apps/plugins-workspace. Shell plugin also owns the
sidecar mechanism (step 6, model a).
5. Sidecar / external binary
- Light, one-shot external program → Tauri shell-plugin sidecar: declare in
plugins.shell+bundle, spawn scoped viaapp.shell().sidecar(...). - Heavy long-lived engine (Python/Go/ML) → hand-rolled
tokio::process::Command
speaking a JSON-lines protocol with id-correlation + generation stamps + anEventSinktrait, exactly as8syncdev/open-musik:src-tauri/src/sidecar.rs
(pattern P1)..kill_on_drop(true); clean up inRunEvent::Exit(P3).
6. Bundling + signing + auto-updater
bundle.targets: subset (["deb","appimage"]) or"all". Per-OS inbundle.{macOS,windows,linux}. Linux needswebkit2gtk-4.1deps (P6).- Updater:
plugins.updater = {pubkey, endpoints[]}andbundle.createUpdaterArtifacts: true.tauri signer generate→TAURI_SIGNING_PRIVATE_KEY; pubkey in config. Full production reference:8syncdev/zus:src-tauri/tauri.conf.json(P10); per-profile overlays viatauri build --config tauri.<profile>.conf.json(8syncdev/sidex, P10). - macOS:
signingIdentity,entitlementsplist, notarization env. Windows:webviewInstallMode, optional authenticode.
7. Run + UI VERIFY (never claim a UI works unseen)
cargo tauri dev(orpnpm tauri dev) to launch. Run it as a managed process
viahubop:"start"(name"tauri-dev",ready.logmatching the dev URL,ready.port= yourdevUrlport) so the webview stays alive across calls.- Drive the webview with the
browsertool (xd://browser):openthedevUrl,runtab.observe()/tab.screenshot()to confirm rendering, then
exercise the actual IPC round-trip a user depends on (click the button → assert
theinvokeresult /listenevent landed). For visual regression use8sync shot <url> -o /tmp/x.pngand8sync diff-imgagainst a baseline. - Blank/black Linux window → apply the WebKitGTK env hardening from P9 before the
webview is built;WEBKIT_DISABLE_DMABUF_RENDERER=1is the usual fix. - DevTools auto-on in debug;
tauri = { features = ["devtools"] }for release.
8. Build loop with engine_* (verify = real build/test)
- Record the work:
engine_plan— one slice per capability, each task'sverify= the project's real gate, e.g.cargo tauri build(bundle target),cargo test(thesrc-tauriRust tests), and/orpnpm -C app build(frontend). engine_next→ do the task →engine_verifyruns its verify commands (ALL
must pass) →engine_advancemarks it done (and commits if asked).engine_statusshows per-slice progress / blockers across the batch. Delegate
independent slices (e.g. "frontend wrapper", "macOS signing") totask
subagents — but keep capability/IPC changes in one slice; they share a schema.- After a Rust change,
mcp__serena__get_diagnostics_for_filere-checks the file
before you spend acargo tauri buildcycle.
9. Capture durable decisions
retainproject-specific facts the next session needs: the sidecar protocol
shape, the capability layout, the updater endpoint, the bundle target set.recall/reflectthem at the start of the next Tauri task on this repo.
Acceptance check (all must hold before "done")
cargo tauri devlaunches the window (managedhubprocess reaches ready);
no "command not allowed" / "command not found" in the console.- Capabilities validate: every command/plugin the app invokes has a granted
permission in the window's capability file (verified againstgen/schemas/desktop-schema.json). - The IPC round-trip a user depends on is exercised, not asserted: drive the
webview viabrowser/8sync shot, fire the realinvoke, observe the real
result orlistenevent. Screenshot evidence saved for any visual claim. - The target bundle builds:
cargo tauri buildsucceeds for the configuredbundle.targets; if the updater is configured, signature artifacts
(*.sig) +latest.jsonare emitted (createUpdaterArtifacts: true). engine_verify(verify = the build/test above) is green for the changed slice.
Non-goals
- Mobile (iOS/Android) unless the project already targets it — only the
lib/main split +crate-typenote from base §1 apply. - Maintaining a v1-only app with no v2 migration intent.
- Rewriting or swapping the frontend framework (React/Vue/Svelte/bun — Tauri is
agnostic; this skill treats the webview as a black box youinvoke/listen). - Vendoring upstream docs —
references/base.mddistils and links; always defer
to https://v2.tauri.app/ for the full, current surface.