Run the desktop's model layer in the browser - #557
Open
gschier wants to merge 6 commits into
Open
Conversation
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.
Member
Author
Greptile SummaryThe PR runs the desktop model layer in browsers through a WASM-backed database owned by a SharedWorker.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/platform/src/web/worker.ts | Implements the SharedWorker database owner, lifetime Web Lock, WASM boot sequence, RPC handling, and cross-tab event broadcasts. |
| packages/platform/src/web/connection.ts | Connects browser tabs to the SharedWorker and reports unsupported browsers or worker startup failures without creating a competing fallback owner. |
| packages/platform/src/web/commands.ts | Routes supported browser commands to fixed handlers and returns explicit capability errors for unsupported operations. |
| crates/yaak-web/src/lib.rs | Exposes the WASM model database, RPC, and blob operations backed by the IndexedDB SQLite VFS. |
| crates/yaak-models/src/models_ops.rs | Relocates model operations into the shared crate so desktop and browser hosts use the same behavior. |
| apps/yaak-client/vite.config.ts | Selects and bundles the browser platform entry and worker/WASM assets for the web target. |
Sequence Diagram
sequenceDiagram
participant Tab as Browser Tab
participant SW as SharedWorker
participant Lock as Web Lock
participant WASM as yaak-web WASM
participant DB as IndexedDB SQLite VFS
Tab->>SW: connect(port)
SW-->>Tab: hello
SW->>Lock: acquire database-owner lock
Lock-->>SW: lock granted
SW->>WASM: boot()
WASM->>DB: open and migrate database
WASM-->>SW: ready
SW-->>Tab: ready
Tab->>SW: RPC command
SW->>WASM: rpc(command, payload, tab label)
WASM->>DB: query or mutation
WASM-->>SW: result and model writes
SW-->>Tab: result
SW-->>Tab: broadcast model_writes
Reviews (5): Last reviewed commit: "Simplify the worker connection" | Re-trigger Greptile
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.
wasm-bindgen glue, ts-rs bindings and lockfiles are build output; reviewing them spends tokens on code nobody wrote. Same list vite.config.ts excludes from lint.
Member
Author
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.
Member
Author
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.
Member
Author
Drop the reconnect-and-retry loop. The one thing it recovered from — a module worker missing connect events during a top-level-await import — is fixed at the source by importing the wasm lazily, and it has not fired since. A worker that stays silent past a generous timeout now shows a message rather than being retried; a reload is the right remedy anyway. Pending requests no longer keep copies for replay, and blob bodies are transferred. Also correct the browser-support wording: every current browser, desktop and mobile, has SharedWorker and Web Locks (Chrome for Android since 148), so this is not a targeting decision — only browsers years out of date are told they can't run Yaak.
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Slice 1 of web.yaak.app: the real
apps/yaak-clientboots in a plain browser tab and stores its data throughyaak-modelscompiled to wasm — the desktop's own queries, 70 migrations, cascade rules, duplicate naming and first-run bootstrap — rather than a TypeScript port of them. Follows #556.crates/yaak-web(@yaakapp-internal/web):boot()registers an IndexedDB-backed VFS and callsinit_standalone;rpc(cmd, payload, label)answers themodels_*commands throughClientDband returns themodel_writesit caused; blob get/put viablob_manager. Built likeyaak-templateswithpkg/committed. Needs a wasm-capable clang to rebuild; the build script keepspkg/and says so when there isn't one, so a desktopnpm run bootstrapnever depends on itmodels_opsmoves fromcrates/yaakintoyaak-models(the send engine doesn't build for wasm)packages/platform/src/websheds the IndexedDB model layer from the earlier draft;commands.tsforwards model commands to the worker and keeps the fixed answers and refusals-with-a-reasonYAAK_TARGET=webselects the browser entry (index.web.ts); Settings and workspace-switch open in-tab whenmultiWindowis false. Desktop bundle unchanged (verified: no@tauri-appsin the web bundle, Tauri host intact in the desktop one)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 via the worker, Send declined with a toast, 8/8 reloads rendering in ~100 ms.
Not in scope: sending (slice 2, hosted proxy), plugins, git/sync, encryption. Known gaps and what slice 2 needs from this layer are in
packages/platform/src/web/README.md.