# Tauri

Use this playbook to design, build, secure, test, and release a Tauri v2
application.

This guidance targets Tauri v2, as the official documentation describes it on
the last updated date. Recheck the linked documentation before each major
release.

The one rule: minimize the authority reachable from the webview. Then enforce
every important rule again inside the Rust core.

## Core model

A Tauri application has two trust zones.

- The webview renders the user interface.
- The Rust core owns native authority.

Treat all webview input as untrusted. A frontend defect can become native code access through IPC.

The Rust core must enforce every security rule. Frontend validation improves usability but does not provide security.

Tauri uses the system webview. Windows uses WebView2, macOS uses WKWebView, and Linux uses WebKitGTK.

The installed webview version can differ by platform and device. Test real target platforms and supported operating system versions.

Sources: [process model](https://v2.tauri.app/concept/process-model/), [webview versions](https://v2.tauri.app/reference/webview-versions/), [runtime authority](https://v2.tauri.app/security/runtime-authority/).

## When to use it

Choose Tauri when all of these conditions apply.

- The product needs a desktop or mobile application.
- The team can maintain Rust and web code.
- Small bundles or native integration are important.
- The user interface works as static web assets.
- The team will test different system webviews.

Choose another design when any of these conditions apply.

- The application requires a bundled, identical browser runtime.
- The application depends on server-side rendering inside the desktop process.
- The team cannot support platform-specific packaging and signing.
- The application can remain a normal website without native access.

Tauri serves static assets. Use an SPA, SSG output, or a classic multi-page application.

Do not place an SSR server inside the application without a specific requirement. Use a normal client-server API boundary.

Source: [frontend configuration](https://v2.tauri.app/start/frontend/).

## Baseline

Use a small baseline unless product requirements require more.

- Tauri v2.
- Stable Rust through `rustup`.
- TypeScript for a JavaScript frontend.
- Vite for an SPA.
- One package manager and one lockfile.
- Official Tauri plugins when they meet the requirement.
- Platform-native CI runners for release builds.

Commit these files.

- `Cargo.lock`.
- The frontend package lockfile.
- `src-tauri/tauri.conf.json`.
- All capability and permission files.
- Platform configuration files.
- The updater public key.

Never commit these items.

- Signing private keys.
- Signing passwords.
- Updater private keys.
- API secrets.
- Production user data.

## Build order

### 1. Create the project

Install the platform prerequisites first. Tauri requires different native tools on Linux, macOS, and Windows.

Source: [prerequisites](https://v2.tauri.app/start/prerequisites/).

Create a standard project with the official scaffold.

```sh
pnpm create tauri-app
cd APP-NAME
pnpm install
pnpm tauri dev
```

Replace the following:

- `APP-NAME`: the project name that you gave the scaffold.

Use the manual initializer only when you already have a frontend.

Source: [create a project](https://v2.tauri.app/start/create-project/).

For Vite, align the development port with `build.devUrl`. Set `build.frontendDist` to the generated static directory.

```json
{
  "build": {
    "beforeDevCommand": "pnpm dev",
    "beforeBuildCommand": "pnpm build",
    "devUrl": "http://localhost:5173",
    "frontendDist": "../dist"
  }
}
```

Configure Vite to ignore `src-tauri`. This prevents unnecessary rebuild work.

Source: [Vite configuration](https://v2.tauri.app/start/frontend/vite/).

### 2. Use a durable project structure

Keep framework code separate from application logic.

```text
APP-NAME/
├── src/
│   ├── app/                 # startup and routing
│   ├── features/            # product features
│   ├── components/          # reusable UI
│   ├── lib/tauri/           # typed IPC client
│   └── types/               # shared frontend types
├── src-tauri/
│   ├── capabilities/        # window and webview privileges
│   ├── permissions/         # custom command permissions
│   ├── src/
│   │   ├── commands/        # thin IPC adapters
│   │   ├── domain/          # rules and domain types
│   │   ├── services/        # files, network, database
│   │   ├── platform/        # target-specific adapters
│   │   ├── error.rs         # serializable command errors
│   │   └── lib.rs           # builder and registration
│   ├── Cargo.toml
│   └── tauri.conf.json
└── tests/
```

Keep `lib.rs` focused on composition. Do not place all commands and business rules in this file.

Keep commands thin. A command must validate input, authorize work, call a service, and map the result.

Test domain and service code without a webview. This design reduces slow end-to-end tests.

### 3. Design the IPC contract

Use commands for requests that return a result. Use channels for ordered streams.

Use events only for notifications. Events are not type-safe and cannot return values.

#### Use typed request and response objects

Define stable transport types on both sides. Use one naming convention.

```rust
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateNoteRequest {
    title: String,
    body: String,
}

#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct NoteDto {
    id: String,
    title: String,
}
```

Create one frontend IPC module. Do not scatter command names across UI components.

```ts
import { invoke } from "@tauri-apps/api/core";

export type CreateNoteRequest = { title: string; body: string };
export type NoteDto = { id: string; title: string };

export function createNote(input: CreateNoteRequest): Promise<NoteDto> {
  return invoke<NoteDto>("create_note", { input });
}
```

Generate bindings when the selected generator is maintained and reviewed. Generated types do not replace runtime validation.

#### Return structured errors

Return `Result<T, AppError>` from fallible commands. Serialize stable error codes for the frontend.

Do not return internal paths, SQL text, tokens, or debug details to the webview.

```rust
#[derive(Debug, thiserror::Error, serde::Serialize)]
#[serde(tag = "code", content = "details", rename_all = "snake_case")]
enum AppError {
    #[error("The note was not found")]
    NotFound,
    #[error("The input is invalid")]
    InvalidInput { field: String },
    #[error("An internal error occurred")]
    Internal,
}
```

Log the internal cause in Rust. Return a safe error and a correlation identifier to the frontend.

Source: [command error handling](https://v2.tauri.app/develop/calling-rust/#error-handling).

#### Keep the main thread free

Use asynchronous commands for file, network, database, and other slow work.

Synchronous commands run on the main thread unless configured otherwise. Long work can freeze the user interface.

Move CPU-heavy work to a blocking worker. Add cancellation for work that the user can stop.

Set timeouts for network and sidecar operations. Bound queues, retries, payload sizes, and concurrency.

Source: [asynchronous commands](https://v2.tauri.app/develop/calling-rust/#async-commands).

#### Select the correct transport

Use these rules.

- Use commands for request-response operations.
- Use a raw IPC response for large binary results.
- Use channels for progress, chunks, and ordered streams.
- Use targeted events for small notifications.
- Use global events only when every listener needs the event.

Clean up every frontend event listener during component unmount. Async listeners can process rapid events out of order.

Source: [commands, channels, and events](https://v2.tauri.app/develop/calling-rust/).

### 4. Manage state and concurrency

Place long-lived services in Tauri managed state. Retrieve state through `State` or the `Manager` trait.

Use a type alias for wrapped state. This prevents runtime panics from mismatched state types.

```rust
struct AppStateInner {
    repository: NoteRepository,
}

type AppState = std::sync::Mutex<AppStateInner>;
```

Tauri already shares managed state. Do not add `Arc` without another ownership requirement.

Use `std::sync::Mutex` for short synchronous critical sections. Use an async mutex only across required await points.

Never hold a standard mutex guard across an await point. Never perform network or disk work while holding a lock.

Prefer message passing or a dedicated worker for complex mutable resources. Keep lock order explicit when multiple locks exist.

Source: [state management](https://v2.tauri.app/develop/state-management/).

### 5. Place data in the correct location

Use Tauri path APIs and platform base directories. Do not construct platform paths manually.

- Put user configuration in the application config directory.
- Put mutable application data in the application data directory.
- Put caches in the cache directory.
- Put logs in the log directory.
- Use a user-selected path for user documents.

Use narrow file scopes. Prefer one application directory or one selected file over the home directory.

Close file handles after use. Use atomic replacement for important configuration files.

Validate file size and format before parsing. Treat imported files as untrusted input.

Source: [file system plugin](https://v2.tauri.app/plugin/file-system/).

#### Select persistence by data type

- Use a key-value store for small, non-secret preferences.
- Use SQLite for structured, relational, or queryable data.
- Use Stronghold or an operating system credential store for secrets.
- Use normal files for user-owned documents.

Do not put credentials in `localStorage`, plain JSON, logs, or frontend state persistence.

Source: [Stronghold plugin](https://v2.tauri.app/plugin/stronghold/).

Plan schema migrations before the first public release. Make every migration transactional and retry-safe.

Back up valuable data before a destructive migration. Test upgrades from every supported stored schema version.

### 6. Apply least privilege

Tauri capabilities control which windows and webviews can call native APIs. Permissions enable commands and scopes restrict their behavior.

Use one capability file per security role. Reference capability identifiers explicitly from `tauri.conf.json`.

Avoid window wildcards. Avoid capability overlap because privileges merge for matching windows and webviews.

Use platform-specific capabilities when a privilege only applies to one target.

Source: [capabilities](https://v2.tauri.app/security/capabilities/).

#### Start with no plugin authority

Add one command permission for one requirement. Add the narrowest scope that supports the operation.

```json
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "main-documents",
  "description": "The main webview can read selected application documents.",
  "windows": ["main"],
  "platforms": ["linux", "macOS", "windows"],
  "permissions": [
    "dialog:allow-open",
    {
      "identifier": "fs:allow-read-text-file",
      "allow": [{ "path": "$APPDATA/documents/**" }]
    }
  ]
}
```

Confirm each permission identifier against the generated schema. Plugin permissions can change across releases.

Do not grant a plugin `default` permission set without reviewing every included command.

Use deny scopes for sensitive subdirectories. A deny scope takes precedence over an allow scope.

Source: [permissions](https://v2.tauri.app/security/permissions/), [command scopes](https://v2.tauri.app/security/scope/), [plugin permission workflow](https://v2.tauri.app/learn/security/using-plugin-permissions/).

#### Restrict custom commands

Register only required commands. Custom commands are otherwise available to local application webviews by default.

Declare custom commands in the application manifest when different webviews need different command access.

Enforce custom scope rules inside each command. Tauri passes scopes, but the command implements the validation.

Audit path normalization, URL parsing, glob rules, case handling, symbolic links, and deny precedence.

Source: [custom command capability guidance](https://v2.tauri.app/security/capabilities/), [scope enforcement](https://v2.tauri.app/security/scope/).

### 7. Harden web content

Bundle the production frontend with the application. Do not load the main interface from a remote URL.

Avoid remote scripts, styles, fonts, and CDN assets. A remote asset can change without an application release.

Import Tauri APIs from packages. Keep `app.withGlobalTauri` disabled unless a reviewed integration requires the global object.

Set a strict Content Security Policy. Tauri only applies CSP protection when the configuration defines it.

```json
{
  "app": {
    "security": {
      "csp": {
        "default-src": "'self'",
        "connect-src": "'self' ipc: http://ipc.localhost https://api.example.com",
        "img-src": "'self' asset: http://asset.localhost blob: data:",
        "style-src": "'self'",
        "font-src": "'self'",
        "object-src": "'none'",
        "base-uri": "'none'",
        "frame-ancestors": "'none'"
      }
    }
  }
}
```

Adapt the policy to actual requirements. Do not add `'unsafe-eval'` or broad hosts to fix an unexplained error.

Some frontend tools need a different development policy. Keep development exceptions out of production configuration.

Source: [Content Security Policy](https://v2.tauri.app/security/csp/).

Use the isolation pattern for an additional IPC inspection layer when practical. It does not replace Rust validation.

Source: [isolation pattern](https://v2.tauri.app/concept/inter-process-communication/isolation/).

#### Handle links and remote content safely

Open normal web links in the system browser. Do not navigate a privileged application webview to arbitrary content.

If remote content must appear, place it in a separate webview with no native permissions.

Do not grant remote IPC access unless the product cannot work without it. Pin exact trusted origins and minimize permissions.

Domain ownership must remain secure for the complete application lifetime. A domain takeover can become native compromise.

#### Validate every command input

Validate these properties in Rust.

- String length and character rules.
- Numeric range.
- Collection length.
- File size and format.
- Normalized file path and allowed root.
- Parsed URL scheme, host, port, and path.
- Identifier ownership and current user authority.
- Operation state and replay behavior.

Use parser types instead of string prefix checks. Canonicalize paths before authorization when the operation permits it.

Prevent time-of-check to time-of-use errors. Open files with safe platform flags when replacement matters.

### 8. Control native execution

Prefer a Rust library over a shell command. Prefer a fixed sidecar over an arbitrary system executable.

Never concatenate user input into a shell string. Pass fixed executables and separate arguments.

Allow only named commands, fixed argument forms, and required working directories. Set execution timeouts and output limits.

Package one sidecar for each target triple. Verify the sidecar source, license, signature, and update process.

Source: [embedding external binaries](https://v2.tauri.app/develop/sidecar/).

### 9. Manage plugins and dependencies

Prefer official plugins for common platform functions. Review each plugin's maintenance, permissions, scopes, and native code.

Remove unused plugins and Cargo features. Every dependency increases build time, bundle size, and supply-chain exposure.

Use these automated checks in CI.

- Rust formatting and linting.
- Rust tests.
- Frontend formatting, linting, type checks, and tests.
- Rust dependency advisories.
- Frontend dependency advisories.
- License policy checks.
- Secret scanning.
- Generated capability schema validation.

Pin CI actions to reviewed commit hashes for high-assurance releases. Review automated dependency updates before merging.

Build from committed lockfiles. Record toolchain and runner versions for release reproduction.

Update Tauri promptly after a security release. Use Tauri 2.11.1 or later: it fixes remote-origin ACL bypasses in specific configurations.

Source: [Tauri 2.11.1 security fixes](https://v2.tauri.app/release/tauri/v2.11.1/), [ecosystem security](https://v2.tauri.app/security/ecosystem/).

### 10. Design for platform differences

Do not assume browser behavior is identical across system webviews. Avoid new web features without support verification.

Test these areas on every supported platform.

- Font metrics and text rendering.
- Focus, keyboard shortcuts, and input methods.
- File dialogs and path behavior.
- Window size, scale, and multiple monitors.
- Dark mode and accessibility settings.
- Clipboard and drag operations.
- Deep links and single-instance behavior.
- Sleep, resume, shutdown, and application exit.
- Offline startup and proxy behavior.

Use target-specific Rust modules and configuration files. Keep conditional logic out of normal domain code.

Build Windows artifacts on Windows and macOS artifacts on macOS. Cross-compilation can have packaging and signing limits.

Source: [Windows installer guidance](https://v2.tauri.app/distribute/windows-installer/).

#### Add mobile requirements deliberately

Use separate mobile capability files with the mobile schema. Do not expose desktop-only authority on mobile targets.

Tauri capability permission and operating system permission are separate controls. Satisfy both controls for protected mobile features.

Request an operating system permission only when the user starts the related feature. Explain the reason before a sensitive request.

Handle denial, restricted status, and later revocation. Do not make basic startup depend on an optional permission.

Test physical devices in addition to simulators. Test backgrounding, resume, rotation, safe areas, deep links, and memory pressure.

Expose the development server only on a trusted network. A physical mobile device must reach that server during development.

Sources: [mobile plugin permissions](https://v2.tauri.app/develop/plugins/develop-mobile/#permissions), [application lifecycle threats](https://v2.tauri.app/security/lifecycle/).

### 11. Make window lifecycle explicit

Define the expected behavior for each window.

- Create or reuse.
- Hide or destroy on close.
- Restore focus.
- Persist size and position.
- Handle invalid saved monitor coordinates.
- Release listeners and resources.

Do not create duplicate windows after rapid user actions. Use stable labels and idempotent creation logic.

Do not assume closing the main window exits a tray application. Test explicit exit and operating system shutdown behavior.

Give untrusted or less trusted windows separate capabilities. Multi-webview windows should use webview-level capability matching.

### 12. Optimize measured problems

Measure startup time, command latency, memory, CPU, binary size, and packaged size on release builds.

Apply these changes when measurements justify them.

- Lazy-load large frontend features.
- Defer noncritical startup work.
- Batch repeated IPC calls.
- Stream large data through channels.
- Return raw bytes instead of JSON byte arrays.
- Move heavy work away from the main thread.
- Remove unused Rust features and frontend packages.
- Cache only immutable or correctly invalidated results.

Do not optimize only the Rust binary. Frontend JavaScript, images, fonts, and startup requests can dominate user experience.

Use development instrumentation only in debug builds.

Source: [large responses and channels](https://v2.tauri.app/develop/calling-rust/), [debug-only DevTools](https://v2.tauri.app/develop/debug/crabnebula-devtools/).

### 13. Use a layered test strategy

#### Fast tests

- Test Rust domain rules with unit tests.
- Test service adapters with controlled fixtures.
- Test frontend components in a browser test environment.
- Mock `invoke` at the frontend IPC boundary.
- Test transport serialization and error mapping.

Tauri provides frontend IPC mocks and a Rust mock runtime.

Sources: [test overview](https://v2.tauri.app/develop/tests/), [mock Tauri APIs](https://v2.tauri.app/develop/tests/mocking/).

#### Integration tests

- Test database migrations on copied databases.
- Test file scopes with allowed and denied paths.
- Test custom command authorization.
- Test sidecar failure, timeout, and malformed output.
- Test offline and partial network failures.

#### End-to-end tests

Use WebdriverIO with the Tauri service for cross-platform desktop tests. It supports native IPC execution and mocking.

Keep the end-to-end suite focused on critical journeys. Use browser mode for faster renderer-only flows.

Source: [WebDriver testing](https://v2.tauri.app/develop/tests/webdriver/).

#### Release matrix

Test each supported architecture and operating system. Include a clean installation, upgrade, rollback policy, and uninstall behavior.

Run smoke tests against packaged artifacts. Development success does not prove bundle success.

### 14. Add production diagnostics

Use structured logs in Rust and the frontend. Include timestamps, levels, module names, and correlation identifiers.

Write logs to the platform application log directory. Set rotation, retention, and maximum size.

Never log these values.

- Passwords.
- Tokens.
- Signing material.
- Full personal records.
- Sensitive file contents.
- Unnecessary absolute paths.

Make diagnostic export user-controlled. Redact sensitive values before export.

Source: [logging plugin](https://v2.tauri.app/plugin/logging/).

Capture these operational events without sensitive payloads.

- Application version and platform.
- Startup stages and duration.
- Migration results.
- Command category and duration.
- Update checks and installation state.
- Sidecar exit category.
- Unhandled frontend and Rust failures.

### 15. Build and release safely

Use a protected release workflow. Require review before release publication.

Perform these steps.

1. Set one authoritative application version.
2. Run all static checks and tests.
3. Build each target on an appropriate runner.
4. Sign each application and installer.
5. Notarize macOS artifacts when required.
6. Generate updater artifacts and signatures.
7. Test packaged artifacts.
8. Publish a draft release.
9. Review artifacts and release notes.
10. Publish the release and update metadata.

Tauri recommends the version in `tauri.conf.json`. Most distribution targets require code signing.

Source: [distribution overview](https://v2.tauri.app/distribute/).

#### Protect signing systems

Store signing credentials in protected CI secrets or a dedicated signing service. Restrict release workflow access.

Use short-lived credentials when the platform supports them. Rotate compromised credentials and document the response procedure.

Do not expose secrets to pull-request workflows from untrusted branches. Do not print encoded certificates or passwords.

Sources: [macOS signing](https://v2.tauri.app/distribute/sign/macos/), [Windows signing](https://v2.tauri.app/distribute/sign/windows/).

#### Configure updates defensively

Use HTTPS updater endpoints. Tauri enforces TLS in production unless an unsafe option disables it.

Keep the updater private key offline or in protected release infrastructure. Back it up securely.

The embedded updater public key verifies signatures. Losing the private key can block updates for installed applications.

Do not enable downgrade support without a signed rollback procedure. Test update interruption and recovery.

Use staged rollout when product risk requires it. Keep a previous signed release available for operational recovery.

Source: [updater plugin](https://v2.tauri.app/plugin/updater/).

## Change workflow

Use this sequence for each native feature.

1. Write the user outcome and failure states.
2. Identify all untrusted input.
3. Design the smallest IPC contract.
4. Put business logic in a Rust service.
5. Add structured request, response, and error types.
6. Implement Rust validation and authorization.
7. Register only the required command.
8. Add the narrowest capability, permission, and scope.
9. Add a typed frontend wrapper.
10. Add unit and integration tests.
11. Add one critical end-to-end path.
12. Test the packaged build on each affected platform.
13. Review logs, diagnostics, and privacy effects.
14. Document recovery and update behavior.

## Anti-patterns

| Anti-pattern | Failure mode | Accepted pattern |
|---|---|---|
| Trust frontend validation | A compromised webview bypasses it | Repeat validation and authorization in Rust |
| Load the production UI remotely | A server or domain compromise gains application reach | Bundle the UI locally |
| Set `csp` to `null` | XSS has fewer containment controls | Define and test a strict CSP |
| Enable the global Tauri object | Any frontend script can discover a broad API surface | Import required APIs from packages |
| Add broad CSP hosts | Any allowed origin can increase attack reach | Allow exact required origins |
| Enable remote IPC broadly | Remote content receives native authority | Avoid remote IPC or isolate exact origins |
| Use `windows: ["*"]` | Every matching window gains authority | Name each privileged window or webview |
| Overlap capabilities casually | Tauri merges their privileges | Use distinct, reviewed security roles |
| Grant plugin defaults blindly | The frontend receives unused commands | Review and grant individual permissions |
| Scope the full home directory | One frontend defect exposes user files | Scope one application directory or selected file |
| Treat scopes as automatic | Custom commands can ignore their scope | Enforce and audit scopes inside commands |
| Accept raw user paths | Traversal and link behavior can escape intent | Normalize, authorize, and safely open paths |
| Build shell command strings | User input can change command meaning | Use fixed executables and argument arrays |
| Put business rules in commands | Logic becomes hard to test and reuse | Use thin commands and tested services |
| Scatter `invoke` calls | Contracts drift without review | Use one typed frontend IPC module |
| Return error strings only | The UI parses unstable text | Return stable error codes and safe messages |
| Use `unwrap` on input paths | A normal error terminates the process | Return typed errors |
| Run slow synchronous commands | Work blocks the main thread | Use async commands or blocking workers |
| Hold a lock during I/O | Requests stall or deadlock | Copy required state, release the lock, then await |
| Send large data as JSON | Serialization increases latency and memory | Use raw responses or channels |
| Use events for request-response | No type or reply contract exists | Use commands |
| Use events for ordered streams | Async handlers can reorder work | Use channels |
| Leak event listeners | Duplicate work grows over time | Store and call each unlisten function |
| Persist secrets in web storage | Frontend code can read the secrets | Use Stronghold or an OS credential store |
| Log command payloads by default | Logs collect secrets and personal data | Log metadata and redacted fields |
| Test only one webview | Platform-specific defects reach users | Test every supported target |
| Treat Tauri permissions as OS permissions | Mobile features fail or surprise users | Design both permission layers |
| Test only development builds | Packaging and CSP defects remain hidden | Test signed or release-mode bundles |
| Cross-compile every release | Packaging and signing behavior can differ | Use native platform runners |
| Ship unsigned installers | Users see warnings and cannot verify origin | Sign and notarize as required |
| Use an HTTP updater | Network attackers can alter metadata traffic | Use HTTPS plus mandatory update signatures |
| Store updater keys in the repository | Repository access enables malicious updates | Protect and back up private keys |
| Enable devtools in release builds | Diagnostics expose internals and add attack surface | Gate devtools with `debug_assertions` |
| Depend on asset secrecy | Bundled frontend assets can be extracted | Keep secrets and enforcement in Rust or servers |

## Verification

### Review checklist

Use this checklist for each pull request.

#### Architecture

- [ ] The command is a thin adapter.
- [ ] Domain logic has direct tests.
- [ ] The IPC contract uses explicit types.
- [ ] The change handles cancellation and timeout requirements.
- [ ] The change avoids unnecessary global state.

#### Security

- [ ] Rust validates all untrusted input.
- [ ] Rust performs authorization before work.
- [ ] The capability names exact windows or webviews.
- [ ] The permission enables only required commands.
- [ ] The scope covers only required resources.
- [ ] The CSP remains strict.
- [ ] No secret enters frontend storage or logs.
- [ ] Remote content receives no native authority.
- [ ] Shell and sidecar arguments cannot change command meaning.

#### Reliability

- [ ] Errors use stable codes.
- [ ] Slow work does not block the main thread.
- [ ] Locks do not cover I/O or await points.
- [ ] Event listeners have cleanup.
- [ ] Streams and queues have bounds.
- [ ] Data migrations are retry-safe.

#### Portability

- [ ] Paths use Tauri or platform APIs.
- [ ] The change accounts for different system webviews.
- [ ] Target-specific code stays behind an adapter.
- [ ] The CI matrix covers affected targets.

#### Checks

- [ ] Rust formatting, linting, and tests pass.
- [ ] Frontend formatting, linting, type checks, and tests pass.
- [ ] Capability schemas validate.
- [ ] Dependency and license checks pass.
- [ ] A packaged artifact passes its smoke test.

### Release checklist

- [ ] The support matrix names operating systems and architectures.
- [ ] Every target builds from committed lockfiles.
- [ ] Every target passes a packaged smoke test.
- [ ] The capability set has a manual least-privilege review.
- [ ] Production CSP has no unexplained exception.
- [ ] Dependency and secret scans pass.
- [ ] Database upgrades work from supported versions.
- [ ] Signing succeeds for each required artifact.
- [ ] macOS notarization and stapling succeed when required.
- [ ] Updater metadata and signatures validate.
- [ ] The updater private key has a secure backup.
- [ ] Update interruption recovery works.
- [ ] Logs rotate and redact sensitive data.
- [ ] Crash and support procedures identify the application version.
- [ ] Release notes describe migrations and known limitations.

## Sources

Use these references during implementation and review.

- [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/)
- [Project creation](https://v2.tauri.app/start/create-project/)
- [Frontend configuration](https://v2.tauri.app/start/frontend/)
- [Calling Rust from the frontend](https://v2.tauri.app/develop/calling-rust/)
- [State management](https://v2.tauri.app/develop/state-management/)
- [Capabilities](https://v2.tauri.app/security/capabilities/)
- [Permissions](https://v2.tauri.app/security/permissions/)
- [Command scopes](https://v2.tauri.app/security/scope/)
- [Content Security Policy](https://v2.tauri.app/security/csp/)
- [Isolation pattern](https://v2.tauri.app/concept/inter-process-communication/isolation/)
- [Official plugins](https://v2.tauri.app/plugin/)
- [Testing](https://v2.tauri.app/develop/tests/)
- [Distribution](https://v2.tauri.app/distribute/)
- [Updater](https://v2.tauri.app/plugin/updater/)