mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
Issue #107: Web build: data-safety fixes for libraries kept in the browser
- File > Back Up Library... (web): a zip of the catalog files and every stored original, built entry by entry; Restore Library from Backup... reads it into a new library folder and switches only when complete (the previous library stays in storage). - Failed flushes to browser storage make the catalog Store refuse writes, so changes show as unsaved (top-bar warning) and are retried every 2 s instead of being lost silently. - A picked/dropped photo whose bytes can't be stored is not added. - One tab per library (Web Locks); a second tab shows a message. - Notices: storage not persistent, library failed to open (temporary session), experimental build; ?reset asks first; a panic shows a message instead of a frozen page; long toasts wrap. - Hosting: _headers/.htaccess no longer mark the unhashed .js/.wasm immutable; HOSTING.md rewritten for the actual build. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
572b55a44a
commit
ebc73bb7b4
19 files changed
+985
-95
No files matched your search
+40
-8
@@ -54,7 +54,8 @@ gzip size (5.25 MB). `wasm-opt -Oz`, when installed, shrinks it further.
|
||||
|
||||
## Deploying: headers
|
||||
|
||||
`cargo xtask web --serve` sends these on every response, and a production server should too:
|
||||
`cargo xtask web --serve` sends these on every response. A production server may send them too;
|
||||
today they are optional:
|
||||
|
||||
```
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
@@ -62,11 +63,13 @@ Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Resource-Policy: same-origin
|
||||
```
|
||||
|
||||
COOP + COEP make the page *cross-origin isolated* (`crossOriginIsolated === true`). That is
|
||||
COOP + COEP make the page *cross-origin isolated* (`crossOriginIsolated === true`). That will be
|
||||
**required** for `SharedArrayBuffer`, i.e. for a future wasm-threads build (`+atomics`, nightly
|
||||
`build-std`), and gives full-resolution `performance.now()`. The current render workers (below)
|
||||
don't share memory, so the app also runs without them; with COEP on, every subresource must be
|
||||
same-origin or send CORP/CORS headers (the bundle has no third-party resources).
|
||||
don't share memory, so the app runs without them; with COEP on, every subresource must be
|
||||
same-origin or send CORP/CORS headers (the bundle has no third-party resources). The file names
|
||||
are the same in every version, so serve them with `Cache-Control: no-cache` (not `immutable`);
|
||||
`packaging/web/README.md` (shipped as `HOSTING.md`) has the hosting details.
|
||||
|
||||
## What works
|
||||
|
||||
@@ -86,10 +89,33 @@ same-origin or send CORP/CORS headers (the bundle has no third-party resources).
|
||||
- `thumbs/<key>.jpg` + `thumbs/index.json`: the rendered-thumbnail cache, with the desktop's
|
||||
budget (2 GB, least recently used pruned to 80 %).
|
||||
|
||||
A new library starts with the procedural demo photos. The app asks for persistent storage
|
||||
(`navigator.storage.persist()`); without it the browser may evict the data under storage
|
||||
pressure. URL options: `?store=idb` forces IndexedDB, `?store=memory` keeps nothing, `?reset`
|
||||
deletes the stored library first.
|
||||
A new library starts with the procedural demo photos. URL options: `?store=idb` forces
|
||||
IndexedDB, `?store=memory` keeps nothing, `?reset` deletes the stored library first (after a
|
||||
confirmation: it deletes every imported photo too).
|
||||
- **Keeping the library safe** (experimental: the library lives only in this browser):
|
||||
- The app asks for persistent storage (`navigator.storage.persist()`); when the browser doesn't
|
||||
grant it, a notice says the library may be evicted under storage pressure.
|
||||
- **File ▸ Back Up Library…** downloads a zip of the library files and every stored original
|
||||
(`originals/<hash>/<file name>`, so an unzipped backup is browsable; entries are stored, not
|
||||
compressed; at most 4 GB). **File ▸ Restore Library from Backup…** reads such a zip into a
|
||||
new library folder (`library-restored-<time>/`; originals already stored are skipped, every
|
||||
entry's checksum is verified) and only then switches to it (`active-library` names the
|
||||
folder in use) and reloads: the previous library stays in storage. Both are web-only
|
||||
(`file.backupLibrary`, `file.restoreLibrary`; the desktop library is a folder).
|
||||
- A failed save (quota exceeded, storage cleared) is not silent: the catalog then refuses new
|
||||
writes, so commands report `saved in memory but not written`, the top bar shows the unsaved
|
||||
warning (as on the desktop, see `docs/control-protocol.md`), and saving is retried every
|
||||
2 s until it works (`web.stats` → `saveError`).
|
||||
- A picked or dropped photo whose bytes can't be stored is not added (it would be gone after
|
||||
a reload); a notice says why.
|
||||
- One tab at a time: the page holds a Web Lock (`navigator.locks`, `lightcraft-library`); a
|
||||
second tab or window shows "LightCraft is already open in another tab" instead of loading
|
||||
its own copy (two copies would overwrite each other's saves). Browsers without Web Locks
|
||||
aren't protected.
|
||||
- If the stored library can't be opened, a notice says the session is temporary and nothing
|
||||
is saved; the stored library is left as it was.
|
||||
- A panic (wasm is built with `panic = "abort"`) replaces the page with a message saying the
|
||||
stored library is kept and to reload, instead of a frozen canvas.
|
||||
- **Importing photos with no filesystem.** *File ▸ Import Photos…* (<kbd>⌘⇧I</kbd>) opens the
|
||||
browser's file picker. You can also drop files anywhere on the page. The bytes are written to
|
||||
storage, then imported and decoded by the same engine code as the desktop app
|
||||
@@ -119,6 +145,12 @@ same-origin or send CORP/CORS headers (the bundle has no third-party resources).
|
||||
yet fails with "still loading" and works a moment later.
|
||||
- **Durability is "a frame later", not fsync-before-return:** a crash or tab kill in the few
|
||||
milliseconds between a command and its flush loses that command.
|
||||
- **Data-safety gaps still open** (issue #107): a panic in a decoder still ends the page (and a
|
||||
photo that panics while its thumbnail renders can do so again after a reload; there is no
|
||||
`?safe` start that skips rendering yet); removing photos doesn't delete their stored originals
|
||||
(`originals/<hash>`; they are kept, and included in backups); restored-over libraries stay in
|
||||
storage until the site's data is cleared; backups larger than 4 GB need zip64, which isn't
|
||||
written yet.
|
||||
- **Preset files** (import/export `.json`) aren't wired to browser pickers yet.
|
||||
- **Control channel / MCP.** These are desktop-only, because they need a TCP socket.
|
||||
- **AVIF export** is untested in the browser; it's the one encoder that may not be wasm-safe.
|
||||
|
||||
Reference in new issue
Block a user