feat(pwa): make the web build installable, with an in-app update prompt - #1206
Open
njbrake wants to merge 1 commit into
Open
feat(pwa): make the web build installable, with an in-app update prompt#1206njbrake wants to merge 1 commit into
njbrake wants to merge 1 commit into
Conversation
_Note: this PR description was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's._ Adds a web app manifest, maskable icons, a generated service worker, and a prompt to hand over to a new build. This is the only route onto a phone home screen that does not go through an app store, and the web build had no update path at all: a stale client's only recourse was clearing site data, which does not reliably work because OPFS survives it. Three constraints shaped the worker config: - **Precache the app shell only** (about 2MB: index.html, entry chunk, main stylesheet). A `**/*` glob precaches 263 entries and 31MB, because `dist` carries roughly 17MB of wasm (each blob emitted twice) plus multi-MB lazy chunks. The tradeoff is explicit: this is not a work-offline app. - **Do not intercept the API.** `/v1/` and `/.well-known/` are on the navigation fallback denylist and there is no `runtimeCaching`, since a stale API answer is worse than an offline error. SSE streams reach the network untouched. - **Cross-origin isolation must survive.** The app needs `crossOriginIsolated` for OPFS. Cached navigation responses retain nginx's COOP/COEP headers; verified still true after a reload served by the worker. Handover is explicit. `registerType: 'prompt'` means a new build installs and waits, because swapping chunks under a live React tree breaks the session. Reload posts `SKIP_WAITING` then reloads once the worker reports `activated`; with no `clientsClaim`, waiting on `controllerchange` would hang. The prompt renders at the app root so it survives the states where the app cannot render, including the init-error screen. Disabled when Tauri drives the build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Note: this PR description was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.
Adds a web app manifest, maskable icons, a generated service worker, and a prompt to hand over to a new build. This is the only route onto a phone home screen that does not go through an app store, and the web build had no update path at all: a stale client's only recourse was clearing site data, which does not reliably work because OPFS survives it.
Three constraints shaped the worker config:
**/*glob precaches 263 entries and 31MB, becausedistcarries roughly 17MB of wasm (each blob emitted twice) plus multi-MB lazy chunks. The tradeoff is explicit: this is not a work-offline app./v1/and/.well-known/are on the navigation fallback denylist and there is noruntimeCaching, since a stale API answer is worse than an offline error. SSE streams reach the network untouched.crossOriginIsolatedfor OPFS. Cached navigation responses retain nginx's COOP/COEP headers; verified still true after a reload served by the worker.Handover is explicit.
registerType: 'prompt'means a new build installs and waits, because swapping chunks under a live React tree breaks the session. Reload postsSKIP_WAITINGthen reloads once the worker reportsactivated; with noclientsClaim, waiting oncontrollerchangewould hang.The prompt renders at the app root so it survives the states where the app cannot render, including the init-error screen. Disabled when Tauri drives the build.