The desktop shell is one generic .deb/.AppImage distributed via mca/public_releases, not built per-booth, but the backend origin was baked in at build time (VITE_API_BASE, hardcoded to http://127.0.0.1:3000) — the same installer could never point at a different appliance without a rebuild. Adds ConnectScreen (shown before Login in Tauri when no backend is saved), backed by tauri-plugin-store persisting the operator-entered URL across restarts. CSP's connect-src tightens to 'self' only — all backend traffic already routes through tauri-plugin-http/websocket, which run Rust-side and are outside connect-src's reach anyway — and the real access boundary moves to capabilities/default.json's http:default scope, wildcarded so an operator-chosen host is actually reachable. Adds a "Change server" control in Setup (desktop-only) to repoint an already-configured install. While tracing the desktop auth path for this: tauri-plugin-http's fetch() runs through Rust's reqwest, which keeps its own cookie jar separate from the webview, so document.cookie on tauri://localhost never sees the parking_csrf cookie the server sets (open upstream bug, tauri-apps/tauri#13045/#11518). This means the desktop app has likely been silently sending no CSRF header on every mutation since the shell was first built — pre-existing, independent of this change. Fixed by having sessionView() (routes/auth.ts) also echo the CSRF value in the login/me JSON body; the desktop client stashes it in memory and echoes that instead of reading document.cookie. assertCsrf() itself is untouched. Verified end-to-end against a real LAN-bound dev server: login returns a csrfToken matching the cookie, a mutation using the body-sourced token in X-CSRF-Token succeeds (200), and the same mutation without it still correctly 403s.
@parking/desktop — Tauri v2 kiosk shell
A thin native desktop window over the @parking/web SPA. It contains no UI and no business
logic of its own: the window renders the same web app the browser does, so the desktop and the
browser stay identical and never drift. Device/auth/ledger logic stays in @parking/server. See
wiki/decisions/desktop-shell-tauri.md.
How the "same look & functionality" guarantee works
| Source of the UI | |
|---|---|
Dev (tauri dev) |
the window loads http://localhost:5173 — the @parking/web Vite dev server. Edit a component in apps/web → HMR updates the desktop window live. |
Prod (tauri build) |
the window bundles apps/web's built dist/. beforeBuildCommand rebuilds the SPA first. |
There is only one UI codebase (apps/web); this package just wraps it.
Backend connection
The SPA talks to Fastify over HTTP/WS. In a browser that's same-origin (relative /api). In the
desktop build the bundled assets load from tauri://localhost, so set VITE_API_BASE (read at
web build time — see .env.example) to the appliance's Fastify origin, e.g.
http://127.0.0.1:3000. The CSP connect-src in tauri.conf.json is already allowed for that
origin, and the backend must include the Tauri origin in WS_ALLOWED_ORIGINS for the live feed.
Commands
pnpm --filter @parking/desktop dev # native window over the web dev server (HMR)
pnpm --filter @parking/desktop bundle # build the SPA + bundle the desktop app (.deb/.rpm/.AppImage)
buildis a no-op in this package soturbo run buildstays fast — the real desktop bundle (compiles Rust, minutes long) is the explicitbundlescript above.
Requires the Rust toolchain and (on Linux) WebKitGTK 4.1 + libsoup-3 dev libraries. Under WSL2 the window needs a display (WSLg or an X server).
Auto-update
Signed updates are built and published by .gitea/workflows/release.yml on a vX.Y.Z tag, mirrored
to the public mca/public_releases repo (this repo is private; the updater runs on offline-first
field appliances with no Gitea credentials, so its endpoint must be reachable unauthenticated —
see that workflow's header and wiki/decisions/desktop-shell-tauri.md). The updater config and
signing pubkey live in tauri.conf.json; the private signing key is held outside the repo, never
committed.
Not here (deliberately)
Kiosk lockdown (fullscreen/no-decorations) and launching Fastify from the shell are out of scope for the scaffold — on the appliance Fastify runs as its own service and this shell connects to it.