agentleFS
lukemurraynzlukemurraynz/hve-agent-skills/skills/tauri-development/SKILL.md

tauri-development

Skill2 starsChanged 3 months agoMITView on GitHub

Build and ship desktop (and mobile) apps with Tauri v2: Rust core + system webview + JS/TS frontend. Covers project setup, capabilities/permissions security model, IPC (commands/events/channels), sidecars, updater + code signing, and mobile targets. WHEN: tauri init, add tauri to app, wrap web app as desktop app, tauri commands, tauri IPC, tauri capabilities, tauri permissions, tauri sidecar, tauri updater, tauri code signing, tauri mobile, tauri android, tauri ios, migrate tauri v1 to v2, electron vs tauri.

  • Reads credentials
  • Installs packages

5 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

15 rules. 0 are common, 6 are distinctive, and 9 are very specific to this project.

  • 0

    Common

    Most of the 5 edited versions we read keep these.

  • 6

    Distinctive

    This author's own choices. Worth a look.

  • 9

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Before generating production-shaped output with this skill, verify every identifier, command, version, status, and example against the …

    line 17
  2. Distinctive:

    If the app already talks to a real backend API, keep the transport in Rust commands and return typed JSON to JS.

    line 47
  3. Very specific:

    When the Tauri crate lives inside a larger Rust workspace, add an empty [workspace] to src-tauri/Cargo.toml unless you intentionally wire …

    line 48
  4. Very specific:

    If the environment lacks pnpm, use the npm scaffold/install path instead of blocking on tool availability.

    line 49
  5. Distinctive:

    Command palette (Ctrl+K) is expected: Every admin console ships one.

    line 57
  6. Distinctive:

    Run these in order. Do not stop after scaffolding, a "created" Tauri app that hasn't been built/run is not done.

    line 78
  7. Very specific:

    If pnpm is not installed in the active environment, use this equivalent npm flow:

    line 90
  8. Very specific:

    Call it from the frontend entry point with invoke('app_health') and render the result.

    line 106
  9. Very specific:

    Grant only what's used: Check src-tauri/capabilities/default.json, the scaffold ships with a minimal starter capability (core:default).

    line 116
  10. Very specific:

    If you scaffold src-tauri/ inside a larger Rust workspace tree and hit:

    line 130
  11. Distinctive:

    If the user's ask is "make me a Tauri app that does X," steps 1–3 plus the actual X logic (as a Rust command, following the architecture …

    line 144
  12. Distinctive:

    Never bundle a full Node runtime "to keep the Express app", that reintroduces Electron's size and attack surface and defeats the reason to …

    line 165
  13. Very specific:

    Capabilities are default-deny for plugin and core commands: Every window, every plugin command needs an explicit grant in …

    line 173
  14. Very specific:

    Migrating v1 → v2: run tauri migrate, then manually re-review every allowlist entry.

    line 219
  15. Distinctive:

    Operator shells should stay boring: Use the Copilot-style shell pattern only as a layout cue: left rail, primary work surface, compact …

    line 225

“Common” is measured: kept by at least 6 in 10 of the 5 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Tauri Development

Anti-Hallucination Rule

Before generating production-shaped output with this skill, verify every identifier, command, version, status, and example against the current authoritative source for the surfaces this skill covers: official product documentation, the CLI/SDK/reference surface, or the organisation's published standard. Anything that cannot be verified must be marked [VERIFY] and paired with the verification command or source. Do not produce final artifacts from memory when verification is blocked; emit a labelled skeleton with [VERIFY] markers and the verification commands instead.

Read this before scaffolding. Tauri wraps an existing (or new) web frontend in a Rust shell using the OS's native webview: no bundled Chromium. That single fact drives every decision below: what can run where, what needs a permission grant, and what breaks when you port an app that assumed a Node/Electron-style backend.

Quick Reference

PropertyValue
Current majorv2 (stable since Oct 2024, mobile GA; v2.x in active release). v1 is legacy with no ongoing development — don't scaffold v1. [VERIFY v1 support policy: tauri-apps/tauri release notes or blog; SECURITY.md (checked 2026-08-17) only distinguishes <1.0]
Core modelRust binary hosts a native webview (WebView2 on Windows, WKWebView on macOS/iOS, WebKitGTK on Linux, Android System WebView)
FrontendAny JS/TS build (Vite, Next static export, SvelteKit, plain HTML) — must produce static assets, no frontend Node runtime at ship time
IPC#[tauri::command] (request/response), events (fire-and-forget pub/sub), Channel (streaming, v2)
Security modelDefault-deny capabilities/permissions for plugin + core commands in src-tauri/capabilities/*.json; your own app commands are allowed to all windows for bundled/local content (HTTP-loaded windows count as remote and need build.rs registration + a remote.urls grant; opt-in restriction via AppManifest::commands)
Native backend needs (DB, long tasks)Rust commands directly, or a sidecar external binary if the logic must stay in another language/runtime
Distribution.msi/.exe (Windows), .dmg/.app (macOS, needs Developer ID + notarization), .deb/.rpm/AppImage (Linux)
Auto-updateOfficial tauri-plugin-updater — separate Ed25519 signature from OS code signing, both usually needed
MobileAndroid + iOS stable, built through Android Studio / Xcode; smaller plugin ecosystem than React Native/Flutter, no arbitrary process spawning
Related skillsnone in this org yet — this is the first Tauri skill

Project lessons

  • For operator/admin apps, keep the UI as a single desktop shell and move work into the left rail, command bar, and focused pages. Do not add multi-window complexity unless the workflow truly needs it. In Execution Ledger, the useful showcase flows are the BizTalk-style migration and the Drasi change-reaction scenario; keep those as first-class examples instead of burying them in generic test data.
  • If the app already talks to a real backend API, keep the transport in Rust commands and return typed JSON to JS. That avoids CORS drift and keeps auth/header composition out of the frontend.
  • When the Tauri crate lives inside a larger Rust workspace, add an empty [workspace] to src-tauri/Cargo.toml unless you intentionally wire it into the parent workspace.
  • If the environment lacks pnpm, use the npm scaffold/install path instead of blocking on tool availability.

Polish lessons (Execution Ledger operator console, 2026-07-26)

  • Dark theme is table stakes for admin tools. Light-only operator consoles read as internal demos. Use CSS custom properties (not Tailwind-only) for interoperable theming. Define tokens for backgrounds (canvas/surface/elevated/raised), borders (subtle/default/strong/focus), text (primary/secondary/heading/muted), and status colors (success/warning/danger/info) with corresponding background and border tints. Set color-scheme: dark and <meta name="theme-color"> for native chrome integration.
  • "Showcase" language kills credibility. Rename "showcase scenarios" to "job templates" (or "workflows"). The hero panel should describe what the tool does, never frame it as demo content. Remove "scenario lab" and similar tutorial framing from production operator tools.
  • Replace window.confirm() before shipping. Destructive admin operations (lease recovery, poison queue drain) need proper modal dialogs with clear CTA labels. A browser-native confirm dialog signals prototype.
  • Toast notifications > inline error panels. Operator actions produce transient results (triggered, recovered, drained). A toast stack in the bottom-right keeps the workspace clean. Dismiss automatically after 4s. Inline error panels that persist until the next action are noisy.
  • Command palette (Ctrl+K) is expected. Every admin console ships one. Even a simple implementation (filterable list of navigation targets + quick actions) measurably improves operator speed. Wire it as a React portal with keyboard navigation (arrow keys + Enter, Escape to close).
  • Auto-poll every 8s. Manual "Refresh runs" buttons train operators to distrust the UI. Poll silently (catch errors, don't surface them) and let the data flow. The button becomes a backup, not the primary interaction.
  • Sidebar icons are non-negotiable. A text-only rail-nav reads as a wireframe. Inline SVG icons (zero-dependency) give each section visual distinctiveness. Active state highlighting with a brand-tinted background and border makes the current location obvious.
  • Empty states need personality. A centered icon + short message + hint text ("Configure your connection and refresh, or launch a job template") is infrastructure. "No runs loaded yet" as bare text is a demo.
  • Modal dialogs use useEffect for Escape-to-close and body scroll lock. Every modal needs document.addEventListener("keydown", ...) for Escape and document.body.style.overflow = "hidden" (restored on unmount). Click-outside-to-close via overlay onClick with e.target === overlayRef.current check.
  • CSS-only dark scrollbars. ::-webkit-scrollbar with track/thumb colors matching the dark theme palette avoids the jarring default light scrollbar in dark UIs. scrollbar-width: thin + scrollbar-color for Firefox.
  • Keep the sidebar sticky with position: sticky; top: 0; height: 100vh. On long workspace scroll, the sidebar stays in view without JS scroll handlers. On mobile (<780px), switch to position: static and flex-direction: column.

When to Use This Skill

  • Creating a new standalone Tauri app from zero, this skill must be able to produce a working app, not just advice. See the procedure below.
  • Wrapping an existing SPA/web app as an installable desktop app.
  • Migrating a Tauri v1 app to v2 (allowlist → capabilities).
  • Designing IPC between a JS frontend and Rust backend (commands/events/channels).
  • Deciding whether a background process should be a Rust command, a sidecar binary, or should stay a separate HTTP service the webview calls.
  • Setting up the updater, code signing, or CI release pipeline.
  • Adding Android/iOS targets to an existing Tauri desktop app.

Procedure: Scaffold a New Standalone App (from zero, must end functional)

Run these in order. Do not stop after scaffolding, a "created" Tauri app that hasn't been built/run is not done.

  1. Scaffold:

    pnpm create tauri-app@latest my-app --manager pnpm --template react-ts --yes
    cd my-app
    pnpm install

    (swap --template for vanilla, svelte-ts, vue-ts, etc. per the user's stated frontend preference; default to react-ts if unstated since it's the most common in this org.)

    If pnpm is not installed in the active environment, use this equivalent npm flow:

    npm create tauri-app@latest my-app -- --manager npm --template react-ts --yes
    cd my-app
    npm install
  2. Add one real Rust command (proves IPC works end-to-end, don't ship the untouched greet starter as "done"). In src-tauri/src/lib.rs:
    #[tauri::command]
    fn app_health() -> Result<String, String> {
        Ok("ok".into())
    }
    // in the builder: .invoke_handler(tauri::generate_handler![app_health])
    Call it from the frontend entry point with invoke('app_health') and render the result. this is the smallest possible proof the Rust↔JS bridge is wired, matching the ponytail rule that non-trivial logic needs a runnable check.
  3. Run it for real — a background terminal is not verification, the app must actually launch:
    pnpm tauri dev
    Confirm the window opens and the health-check text renders before doing anything else.
  4. Grant only what's used. Check src-tauri/capabilities/default.json, the scaffold ships with a minimal starter capability (core:default). Add permissions only for plugin commands actually called — your own app commands need no grant. Don't pre-grant for imagined future features.
  5. Prove it packages: pnpm tauri build at least once before calling the app "functional." This catches missing icons, bundle-identifier issues, and platform build-tool gaps (Rust toolchain, WebView2, webkit2gtk-4.1 dev packages) immediately instead of at release time.
  6. Only then layer in real features, following the decision tree below for anything that needs a backend, and the Rules section for IPC/capabilities/signing choices.

Nested Rust workspace gotcha (high-frequency in monorepos)

If you scaffold src-tauri/ inside a larger Rust workspace tree and hit:

  • current package believes it's in a workspace when it's not

add an explicit empty workspace table to the Tauri crate:

# src-tauri/Cargo.toml
[workspace]

This isolates the desktop crate from unintended parent workspace membership unless you intentionally wire it into the parent workspace.members.

If the user's ask is "make me a Tauri app that does X," steps 1–3 plus the actual X logic (as a Rust command, following the architecture decision tree if X needs a backend) is the full scope. don't add tray icons, auto-update, code signing, or multi-window support unless asked.

The One Question That Decides the Architecture

Does the app's current backend logic run in the same process as the frontend, or does the frontend just call an HTTP API?

  • Frontend already just calls an HTTP API → Tauri wrap is close to free. Point the webview at the built static assets, keep calling the same API over HTTPS, add auth via system-browser OAuth + deep link (see references). This is the common, low-risk case.
  • Frontend embeds backend logic in the same dev server (a Vite/Next API route, a Node middleware that talks to Postgres/Redis/the filesystem directly) → that code cannot run inside the webview. Pick one, in order of laziness:
    1. Rewrite it to call the real backend service over HTTP if one already exists. Smallest diff, most secure.
    2. Port the logic to a Rust #[tauri::command] if it's small and there's no existing service.
    3. Ship it as a sidecar binary only if it must stay in its original runtime (e.g. an existing Python/Node CLI you can't rewrite). Sidecars add binary-per-platform packaging, process lifecycle, and signing overhead, last resort, not first.

Never bundle a full Node runtime "to keep the Express app", that reintroduces Electron's size and attack surface and defeats the reason to pick Tauri.

Rules

  1. Scaffold with the CLI, not by hand: pnpm create tauri-app@latest (or cargo tauri init into an existing frontend). It wires src-tauri/, tauri.conf.json, and a starter capability file correctly, don't hand-roll the Cargo/Tauri config.
  2. Capabilities are default-deny for plugin and core commands. Every window, every plugin command needs an explicit grant in src-tauri/capabilities/*.json , and a window that matches no capability has no access to the IPC layer at all. Commands you register yourself via generate_handler are allowed to all windows only when the webview loads bundled/local content. If any window loads an HTTP origin at runtime (e.g. a sidecar-served SPA on 127.0.0.1, a remote-hosted UI), that origin is treated as remote: your app commands are denied there until you register them in build.rs (tauri_build::AppManifest::commands, which generates allow-* permissions) and grant those permissions in a capability carrying remote: { urls: [...] }. tauri dev masks this because the dev-server URL is trusted local — the failure only shows up in packaged builds. Restricting via AppManifest::commands is still worthwhile defense-in-depth even for local-only apps (e.g. if you later load remote content). A denied plugin call fails with <command> not allowed. Permissions associated with this command: ... — add the specific named permission it lists, don't widen the whole capability.
  3. Don't reach for tauri-plugin-shell for backend logic. It runs OS commands from the frontend, it's for "open this file in the default app," not a substitute for a real backend. It's also far more limited on mobile (URL-opening only, no process spawning).
  4. Async commands by default; use tokio::sync::Mutex only if a lock must be held across an .await, otherwise plain std::sync::Mutex in State<T> is fine and cheaper. Async commands cannot take borrowed arguments (&str, State<'_, T>) ; use owned String/State<T> or wrap the return type in Result. Never build a window synchronously inside a command handler on Windows, it can deadlock WebView2.
  5. Pick the right IPC primitive: request/response → command. One-off notification → event (remember to unsubscribe listeners on the frontend, e.g. in a useEffect cleanup). Streaming/chunked/high-volume data → Channel (v2), not a loop of emit. For large binary returns (files, downloads) return tauri::ipc::Response instead of JSON-serializing bytes.
  6. Updater signing is separate from OS code signing. You need both for a clean, trusted auto-update: the Tauri Ed25519 keypair (cargo tauri signer generate) signs the update artifact; a Windows Authenticode cert / Apple Developer ID cert satisfies the OS. Store the private signing key only in CI secrets, losing it breaks updates for every existing install. Mechanics: set bundle.createUpdaterArtifacts: true (removed in Tauri v3 ; keep it true, never "v1Compatible" for new apps), pass TAURI_SIGNING_PRIVATE_KEY as a real environment variable in CI (.env files do not work), and put the public key content (not a path) in plugins.updater.pubkey. Updater endpoints are TLS-enforced in production. On GitHub Actions pin tauri-apps/tauri-action to v1 (v1.0.0, Jun 2026, dropped Tauri v1, changed .app.tar.gz artifact naming and made latest.json URLs github.com-based), it builds, signs, and generates the updater latest.json in one step.
  7. Linux is the compatibility tax. Tauri 2 requires webkit2gtk-4.1. WebKitGTK version/glibc drift across distros (LTS releases carry years-old engines) is the single biggest source of "works on my machine" bug reports, and default WebKitGTK builds ship without WebRTC — if the app needs calling/screen-share, Linux needs a custom WebKitGTK build or a different delivery story. Build on the oldest glibc you intend to support, or ship AppImage/Flatpak instead of chasing every distro's webkit2gtk.
  8. Mobile is a second target, not a checkbox. tauri android init / tauri ios init still require Android Studio / Xcode toolchains and per-OS review of every plugin you use, verify each plugin's platform support table before assuming desktop code ports as-is.
  9. Migrating v1 → v2: run tauri migrate, then manually re-review every allowlist entry. the automated conversion is a starting point, not a finished capabilities file. Test every window and every plugin call after migrating; silent plugin-permission denials are the norm until capabilities are complete (your own app commands keep working for local content, the ACL doesn't gate them there by default).
  10. Operator shells should stay boring. Use the Copilot-style shell pattern only as a layout cue: left rail, primary work surface, compact action area, and a clean settings drawer. Good desktop operator UX is mostly about clarity and speed, not visual novelty.

See references/architecture-decisions.md for the backend-integration decision tree and auth pattern, and references/gotchas.md for the full gotcha list with sources.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.