Every review finding on the browser host was in the same forty lines: the
fallback from a SharedWorker to a per-tab worker. Two kinds of worker that
can both come up is a race, and each fix moved it rather than removed it.
The fallback existed for a browser we don't target (Android Chrome, no
SharedWorker) and a tooling limitation. Without it the invariant holds by
browser guarantee — one SharedWorker per origin — with the Web Lock covering
the one overlap the browser doesn't rule out, a reloading tab's dying
predecessor. Reconnect-on-silence stays, but reconnects to the same kind.
Browsers without SharedWorker or Web Locks get a clear message.
Verified in production builds: 8/8 reloads render, two tabs coherent,
unsupported browsers see the message.
Without them nothing can promise a second tab won't open a second SQLite
over the same pages. Refusing with a clear message beats hoping; the
browsers affected (iOS Safari < 15.4, Android Chrome < 69) are already
behind what the app needs.
CI runs `cargo test --all`, which compiled yaak-web for the host and failed
in sqlite-wasm-rs's C shim. The crate is now `#![cfg(target_arch = "wasm32")]`
with its browser-only dependencies target-scoped, so it is empty natively.
Review caught a race in the worker fallback: a shared worker that starts
after the tab has given up on it and taken a dedicated worker would open the
database with no lock. The Web Lock now guards every worker, shared or not —
requested with a short timeout so a reloading tab's dying predecessor is
waited out, then reported. The tab also closes the port it abandoned. Verified
in production builds: 8/8 shared and 3/3 dedicated reloads render, and a
second tab in dedicated mode is told the database is in use.
The browser host now stores data through yaak-models compiled to wasm — the
same queries, migrations, cascade rules, duplicate naming and first-run
bootstrap the desktop and CLI use — instead of a TypeScript port of them.
- crates/yaak-web: the wasm crate. boot() registers an IndexedDB-backed VFS
and calls init_standalone; rpc(cmd, payload, label) answers the models_*
commands via ClientDb and returns the model_writes it caused; blob get/put
through blob_manager. Built like yaak-templates (pkg/ committed); the
build script keeps pkg/ and says so when no wasm-capable clang is present,
so a desktop bootstrap never depends on one.
- models_ops moves from crates/yaak into yaak-models so the wasm crate can
use it without the send engine.
- The database lives in a SharedWorker (packages/platform/src/web/worker.ts):
one process holds the data and pushes writes to every tab, as on the
desktop. Where SharedWorker is missing or its script cannot be fetched, a
dedicated worker guarded by a Web Lock takes over and a second tab is told
so. The worker imports the wasm lazily so a tab's connect is answered
instantly; the tab reconnects if it isn't.
- packages/platform/src/web loses models.ts, schema.ts, db.ts and the
BroadcastChannel; commands.ts forwards model commands to the worker and
keeps the fixed answers and refusals.
Verified in Chrome, dev and production builds: bootstrap, CRUD across every
model type, serde defaults, engine-format ids, Rust copy naming, folder
cascade with per-descendant events, single-event workspace delete, reload
persistence, two tabs coherent, Send declined with a toast, 8/8 reloads
rendering in ~100 ms.