mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Fix #367: keep a paused browser tab's recovery copies
A heartbeat alone can't tell a crashed tab from one whose timers stopped in the background, so another tab could discard (or restore) the copies of a still-live document once its heartbeat went stale. - Recovery stores without locks can now announce an area for as long as the app runs (RecoveryStore::announce/announced). The web shell implements it with the browser's Web Locks (navigator.locks through js_sys::Reflect): a tab holds a lock named after its area, which the browser releases only when the tab is gone. Lock snapshots are refreshed every 10 s (once before the first frame); a snapshot over a minute old counts every area as held. - An area is gone only when no announcement holds it and its heartbeat is stale. Where the store can't tell (no Web Locks), the owner repairs: each heartbeat (also its first frame after a pause) and each recovery save write again the copies missing from its area, and a heartbeat newer than the one another tab wrote when taking the area over marks it running again. - MemoryStore::announcing models Web Locks in tests; engine and UI tests cover the issue's sequence. Native folder locks are unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5865e4cb51
commit
048cbc51b1
11 files changed
+366
-54
No files matched your search
@@ -21,6 +21,7 @@ How the web shell (`apps/vectorcraft-web/src/web.rs`) differs from desktop:
|
||||
- **Open** sets `Services::open_async`, which shows `rfd::AsyncFileDialog`. The bytes arrive in `Services::inbox`, which the app drains every frame.
|
||||
- **Save / Save As / Export** go through `Services::download`: a Blob, an object URL and a temporary `<a download>`, all created from Rust. There is no save dialog, so the suggested name becomes the download name.
|
||||
- **Drag-and-drop:** `WebShell` takes the frame's `dropped_files` before the app sees them, reads each with `DroppedFile::bytes_async` and pushes the bytes into the inbox (files dropped on a canvas go to `Services::place_inbox` with their `place::DropAt`: the document they were dropped on, by uid, and the point). A file that arrives after another document became active still lands in its own document, which becomes active again; if that document closed meanwhile, the file is not placed and the status bar says so. (The app's synchronous drop path is compiled out on wasm32.)
|
||||
- **Data Recovery** keeps its copies in `localStorage` (`vectorcraft-recovery/<area>/<name>`, shared by the site's tabs), so tabs can't lock their areas the way desktop apps lock a folder (`RecoveryStore::lock`). Each tab holds its area with a heartbeat it refreshes every minute and, where the browser has Web Locks (secure pages: `https://` or localhost), with a Web Lock named after the area (`apps/vectorcraft-web/src/locks.rs`, through `js_sys::Reflect` since web-sys has them only as an unstable API). The browser releases the lock only when the tab is gone, not while its timers are paused in the background, which a heartbeat can't tell apart (#367). `navigator.locks.query()` is asynchronous, so the shell keeps a snapshot refreshed every 10 seconds (taken once before the first frame); a snapshot older than a minute counts every area as held. An area is offered as a crash's leftovers only when no tab holds its lock and its heartbeat is older than three intervals. Without Web Locks a paused tab can still be taken for gone; it repairs that when it resumes: its next heartbeat (or recovery save) writes again the copies missing from its area, and a heartbeat newer than the one the other tab wrote taking the area over makes the area running again for that tab.
|
||||
- **No control server:** browsers can't listen on TCP. To automate the web build, drive headless Chrome with `--remote-debugging-port`.
|
||||
- Quick smoke test: `"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new --enable-unsafe-webgpu --screenshot=web.png --window-size=1440,900 --virtual-time-budget=15000 http://127.0.0.1:8766/` (headless Chrome on macOS gets a real WebGPU adapter).
|
||||
|
||||
|
||||
+6
-3
@@ -1554,9 +1554,12 @@ The copies live in the `recoveryFolder` preference's folder (default: `Data Reco
|
||||
none when the app runs with `VECTORCRAFT_NO_PREFS`) or, on the web, in browser storage.
|
||||
|
||||
Each running app (each browser tab) keeps its copies in an area of its own (`<area>/<name>`): a sub-folder whose
|
||||
`.lock` file it keeps locked while it runs, or on the web an area with a heartbeat it refreshes every minute. Only
|
||||
areas nobody holds are offered: their lock is free, or their heartbeat is older than three intervals (at least three
|
||||
minutes). Several apps running at once (agents' instances included) never see each other's copies as crash leftovers,
|
||||
`.lock` file it keeps locked while it runs, or on the web an area with a heartbeat it refreshes every minute and a Web
|
||||
Lock the browser holds until the tab is gone (where the browser has Web Locks: secure pages). Only areas nobody holds
|
||||
are offered: their lock is free, or no tab holds their Web Lock and their heartbeat is older than three intervals (at
|
||||
least three minutes). A background tab whose timers are paused therefore keeps its copies. Without Web Locks another
|
||||
tab can take such a tab for gone; when it resumes, its next heartbeat or recovery save writes its missing copies
|
||||
again. Several apps running at once (agents' instances included) never see each other's copies as crash leftovers,
|
||||
and an area being restored or discarded is held, so two apps launched together never both take it.
|
||||
|
||||
`file.recovery.list` → `{copies: [{file, title, path, format, saved, open, running}], location}` (`open`: the copy of a
|
||||
|
||||
Reference in new issue
Block a user