One React/Vite product across PWA and Tauri—without pretending they are identical
One React/Vite codebase can power a GitHub Pages site, an installed PWA, an edge-hosted deployment, and a Tauri desktop app.
That does not make those surfaces interchangeable.
This distinction matters whenever an application handles user projects, offline behavior, AI providers, filesystem access, or security policy. Shared components are valuable. Shared assumptions can be dangerous.
WorldScript Studio is a useful case study because its React/Vite application is intentionally available across browser/PWA and Tauri desktop environments. The product shares domain semantics, but it does not pretend that a browser tab and a native shell have the same authority boundaries. Code references are from the repository at commit `2d9157c0` (2026-09-28), release v1.28.8; simplified excerpts are labeled.
## Share the product model, not every implementation detail
A cross-platform product needs a stable answer to questions such as:
* What is a project?
* What data belongs in it?
* Which state is authoritative?
* What does export mean?
* What should happen when an AI provider is unavailable?
Those answers should be shared.
The mechanisms underneath them should not be forced to look identical.
For a PWA, the browser provides IndexedDB, Cache Storage, service workers, Web APIs, WebGPU, and origin-scoped storage. A desktop shell can provide application-data filesystem access, native networking, OS integration, and platform packaging.
Trying to hide every difference behind a single universal abstraction often creates a worse outcome: browser APIs leak into desktop code, native assumptions leak into web code, and the product accumulates several accidental definitions of persistence or network policy.
A better rule is:
> Share domain semantics and interoperability contracts. Expose platform capabilities through explicit adapters.
## The host changes the security boundary
The same static build can be deployed to different hosts, but hosting changes what the application can guarantee.
Surface | Important capability | Important limitation
---|---|---
GitHub Pages | Static public deployment | Cannot inject arbitrary HTTP security headers
Vercel / Cloudflare Pages | Response headers and edge-function capabilities | Still browser-origin storage for web project data
Installed PWA | Cached shell and browser-native installation | Storage remains origin- and browser-specific
Tauri desktop | Filesystem persistence and native HTTP | Desktop project files are not currently app-encrypted at rest
GitHub Pages is a good example of why "deployed from the same source" is not enough.
WorldScript uses a meta Content Security Policy there because GitHub Pages cannot add the corresponding response headers — the project's deployment documentation calls that meta tag the _sole enforcement point_ on that host. Vercel and Cloudflare Pages can set real response headers that mirror the meta CSP, and both can run a same-origin edge relay for supported functionality (a Claude proxy lives at `api/claude-proxy.ts` for Vercel and `functions/api/claude-proxy.ts` for Cloudflare, sharing one core module). Those are materially different deployment guarantees, even when users see the same React interface.
The right documentation does not flatten that distinction. It names it.
## Storage is an authority decision, not a convenience API
The PWA's live project path uses browser storage. The desktop path uses filesystem-backed stores under application data.
Both are local. They are not the same.
Browser persistence is governed by the browser's origin, quota, eviction behavior, and storage APIs. Desktop persistence is governed by filesystem access, native process boundaries, and the application's own read/write rules.
That difference becomes especially important for security language. Browser/PWA protected IndexedDB data can use the application's passphrase-based encryption lifecycle when configured. The filesystem-backed desktop project store currently does not receive that same at-rest encryption.
A UI toggle with the same name is not enough to make the protection equivalent. The actual persistence path decides what is protected.
## Native networking changes what "local server" means
A browser connecting to `localhost` is still subject to browser rules such as CORS and Private Network Access. A Tauri desktop application can use an admitted native HTTP capability for local or cloud endpoints.
That does not mean desktop networking is automatically safer. It means its policy must be defined and enforced differently — and "narrowly admitted" is meant literally here. The desktop shell's HTTP capability is an explicit allowlist, not an open pipe:
// src-tauri/capabilities/default.json (excerpt)
{
"identifier": "http:default",
"allow": [
"http://localhost:*/*",
"http://127.0.0.1:*/*",
"https://generativelanguage.googleapis.com/*",
"https://api.openai.com/*"
// …remaining provider hosts, nothing else
]
}
At runtime, the fetch adapter picks its implementation by environment: in the Tauri runtime it dynamically loads the native HTTP plugin; everywhere else it uses the browser's `fetch`. For WorldScript, that desktop-native path supports local inference-server workflows such as Ollama-compatible endpoints without requiring the WebView to bypass browser-origin rules. The browser/PWA path should not silently probe local ports or pretend that the same route will work without user-managed server configuration.
The general lesson is simple:
* Browser security restrictions are product constraints, not annoyances to work around.
* Native capabilities should be narrowly admitted, not made globally available.
* UI copy must say when a feature is desktop-only or depends on local server configuration.
## PWA caching must not leak into desktop behavior
A service worker is a powerful browser feature, but it is not a universal application runtime.
WorldScript's service worker caches its web shell and handles offline fallbacks on web surfaces. In Tauri, the registration code takes the opposite path — it actively tears the browser mechanism down:
// register-sw.ts (excerpt — called only from the Tauri branch)
async function teardownServiceWorkerInTauri(): Promise<void> {
if (!('serviceWorker' in navigator)) return;
const registrations = await navigator.serviceWorker.getRegistrations();
await Promise.all(registrations.map((reg) => reg.unregister()));
const keys = await caches.keys();
await Promise.all(
keys.filter(isWorldScriptOwnedCacheName).map((k) => caches.delete(k)),
);
}
It unregisters any existing service worker _and_ purges the app's own caches. That prevents a stale browser cache from serving an offline fallback over the real bundled desktop application.
This is a subtle example of platform adaptation done well: the feature is not merely disabled because it is inconvenient. It is disabled because its browser lifecycle would be the wrong authority for a native bundle.
## A cross-platform checklist
Before claiming that a web and desktop product are "the same app," ask:
1. Where does each platform persist the authoritative project?
2. Which platform controls headers, CSP delivery, and network policy?
3. Is offline behavior provided by a cached shell, a native bundle, or both?
4. Which integrations need browser permissions, CORS configuration, or native capabilities?
5. Does each storage path have the same encryption and recovery properties?
6. Are platform-specific boundaries visible in the UI and documentation?
A shared codebase is an implementation advantage. It is not a reason to erase the differences users and maintainers need to understand.
The goal is parity where the product contract is shared—and honesty where the platform changes the contract.
_Source note: WorldScript Studio is open source (github.com/qnbs/WorldScript-Studio). Code references correspond to`main` at `2d9157c0` (2026-09-28); release anchor v1.28.8. Key files: `register-sw.ts`, `index.html`, `vercel.json`, `docs/DEPLOYMENT.md`, `src-tauri/capabilities/default.json`, `services/ai/fetchAdapter.ts`, `api/claude-proxy.ts`, `functions/api/claude-proxy.ts`. Part of the "Engineering WorldScript Studio" series._
_AI disclosure: AI tools helped with repository research, structure, and editing of this article. I reviewed the technical claims against the referenced source files before publication._