The desktop app is a Tauri 2 shell wrapping the same Svelte 5 frontend used on the web. The frontend runs inside a WRY webview (WebKit on macOS/Linux, WebView2 on Windows).
┌──────────────────────────────┐
│ Tauri (Rust) │
│ - tray icon, deep links │
│ - IPC commands │
│ - updater plugin │
├──────────────────────────────┤
│ WRY Webview │
│ - Svelte 5 frontend │
│ - Tauri bridges (JS) │
│ - IndexedDB (Dexie) │
└──────────────────────────────┘
Key differences from web:
- No Service Worker — WRY's custom scheme (
tauri://) doesn't support SW registration. Thesync-shim.js+sync-core.jsmodules replace the SW mutation queue. - CSP — The web app's
<meta>CSP tag is stripped; CSP is governed bysrc-tauri/tauri.conf.json→app.security.csp. - Isolation pattern — Tauri's isolation pattern is enabled (
src-tauri/isolation/), adding a security boundary between the webview and IPC.
All platform checks go through src/utils/platform.js:
| Export | Type | Description |
|---|---|---|
isTauri |
boolean |
True inside any Tauri webview |
isTauriDesktop |
boolean |
True on desktop Tauri only |
isTauriMobile |
boolean |
True on mobile Tauri only |
canUseServiceWorker() |
() => boolean |
False on Tauri (WRY doesn't support SW) |
getPlatform() |
() => string |
Returns 'tauri-desktop', 'tauri-mobile', or 'web' |
Provides invokeTauri(command, args) for calling Rust #[tauri::command] functions. Maintains an ALLOWED_COMMANDS allowlist — commands not in the list are rejected client-side before reaching Rust.
Wraps @tauri-apps/plugin-updater with rate limiting (5-min minimum between checks), version validation, and download progress tracking. Also subscribes to WebSocket newRelease events for server-push update notifications.
sync-core.js is a dependency-injected factory (createSyncCore({ postMessage, fetch, indexedDB })) that contains the mutation queue processing logic ported from public/sw-sync.js. sync-shim.js wires it to the main thread with:
CustomEventdispatch aspostMessagewindow.fetchfor network callswindow.indexedDBfor storage- Online/visibility/focus listeners to trigger processing
- 30-second heartbeat for periodic retries
Uses @tauri-apps/plugin-notification for native OS notification channels on desktop.
Selects between the Service Worker path and the sync-shim path based on canUseServiceWorker().
-
Rust: Add a function with
#[tauri::command]insrc-tauri/src/lib.rsand register it in thegenerate_handler![]macro. -
Capabilities: Grant the command in the appropriate capability file under
src-tauri/capabilities/*.json. -
Allowlist: Add the command name to
ALLOWED_COMMANDSinsrc/utils/tauri-bridge.js. -
Call it: Use
invokeTauri('your_command', { arg1: 'value' })from the frontend.
The architecture is designed for future Android/iOS support:
sync-core.jsuses dependency injection — mobile platforms can inject their ownfetchandindexedDBimplementations.platform.jsalready exportsisTauriMobilefor mobile-specific branching.src-tauri/Cargo.tomlusescfg(not(any(target_os = "android", target_os = "ios")))for desktop-only plugins.
- Debug builds:
pnpm tauri:devbuilds in debug mode with DevTools enabled. - Local API: Edit
src/config.jsto pointapiBaseat a local server. - DevTools: Automatically opens in debug builds. Use the Console tab to verify
window.__TAURI_INTERNALS__is defined. - Mutation queue: Star/move a message while offline, go online, and watch the console for
[sync-core]andmutationQueueProcessedmessages.