feat: restore-all session recovery, ?file= deep links, offline PWA

Session recovery: more than one pending snapshot adds a 'Restore all'
button to the Recover Drawing modal — every snapshot goes through the
exact same guarded path as a single restore (deleted only after its
own open succeeds). The open-tab list and active tab persist to
localStorage on web (open/save/close) and the remembered tab is
re-selected once a restore batch settles; the modal notes how many
drawings the session had. Nothing is auto-opened.

Deep links: ?file=<url|name> opens a drawing at boot. http(s) URLs
must end in .dwg/.dxf and are fetched with the same streaming,
64 MB-capped, 8 s-deadline reader the remote images use, then go
through the guarded open queue; cross-origin opens announce the
origin first. Bare names resolve from the browser's recent storage,
with a hint when absent. Everything else (file:, javascript:, drive
letters, non-drawing tails) is refused with a console note.

Offline PWA: the app was installable (manifest) but downloaded its
55 MB bundle on every visit. A cache-first service worker now serves
the whole bundle from Cache Storage after one online visit —
navigations network-first so updates land, hashed assets
stale-while-revalidate, non-GET/cross-origin passthrough, old caches
cleaned on activate, and the waiting-worker update flow documented.
The build wrapper stamps the cache version from the bundle hash and
hard-fails if sw.js is missing or unstamped.

Also fixes a locale group mismatch on the DATALINK pick notice
(catalog said a display group that does not exist in the ftl
sources).
This commit is contained in:
Afonsop committed 2026-09-18 16:27:12 +01:00
1 parent d719fb7778
commit 5f9b345ee6
38 files changed
+1211 -50

No files matched your search

+16
View File
@@ -45,6 +45,7 @@
<link data-trunk rel="copy-file" href="web/videos.json" />
<link data-trunk rel="copy-file" href="web/locale-labels.json" />
<link data-trunk rel="copy-file" href="web/ocs-control.js" />
<link data-trunk rel="copy-file" href="web/sw.js" />
</head>
<body>
<div id="loading">
@@ -53,6 +54,21 @@
<div class="sub">Loading…</div>
</div>
<script src="ocs-control.js" defer></script>
<script>
// Offline support: register the service worker. It is copied next to
// this page by Trunk (see the copy-file link in <head>), so the
// relative URL resolves to /app/sw.js and the registration scope is
// /app/ — the whole app. Registered immediately (not on load) so the
// worker starts capturing this visit's asset requests as early as
// possible. sw.js never reloads the page on update: a new worker
// waits until every tab on the old version has closed.
(() => {
if (!("serviceWorker" in navigator)) return;
navigator.serviceWorker.register("sw.js").catch(error => {
console.warn("service worker registration failed; offline support disabled:", error);
});
})();
</script>
<script>
(() => {
const splash = document.getElementById("loading");