Installable desktop app: PWA (manifest, service worker, install prompt, offline shell) #51

Closed
EduarD3V wants to merge 0 commits from issue/47-installable-desktop-app-pwa-ma into bullet-develop
Owner

Branch merged into bullet-develop during integration. Closing — rolling PR #48 carries the work to main.

Branch merged into bullet-develop during integration. Closing — rolling PR #48 carries the work to main.
name/short_name, standalone display, start_url + scope at the root, theme_color #008d80 and
background_color #fbfaf7 (the app.css tokens: @theme --color-brand, --background), and the PNG
set Chrome/Firefox install needs (192 any, 512 any, 512 maskable). The art is the seal already
drawn in App.svelte, kept as SVG sources so the PNGs are regenerable with rsvg-convert — the
maskable variant holds the mark inside the 80% safe circle.

Co-authored-by: omp <[email protected]>
SvelteKit's built-in service-worker support: src/service-worker.ts is compiled to
build/service-worker.js and registered from the prerendered boot script, so no plugin and no
vite.config.ts change is needed. It precaches everything $app/manifest lists (prerendered pages,
static files, hashed build chunks) under a cache named after the build version, answers an offline
navigation with the cached URL or the cached root, and serves frozen build files cache-first.

Nothing is ever written to a cache at runtime: only same-origin GET requests are handled and there
is no cache.put, so a produced signature cannot be cached. No skipWaiting on install -- the waiting
worker is what the update prompt uses. Precache paths go through $app/paths so a subpath deployment
does not precache the site root. tsconfig excludes src/service-worker.ts explicitly: Kit's own
validator only recognises the file name once the entry exists (pnpm check was warning otherwise).

Refs: verified in Chromium -- 33 entries precached, all HTTP 200, offline / and /sidebar-07 render.

Co-authored-by: omp <[email protected]>
Install app appears only when Chromium fires beforeinstallprompt; the event is cancelled on
arrival so the click can replay prompt(), and the button retires with it because a deferred
prompt is single-use. Update version appears when the registration has a worker waiting, either
already there at mount or installed in the background, and only when an older worker still
controls the page -- on a first install there is no version to be behind. The click posts
SKIP_WAITING; the page reloads once on controllerchange.

app.html gains the manifest link and the teal theme-color meta, i18n gains installApp/updateApp
in both languages, and the buttons sit first in the header action row so an installed app shows
them where the old browser mini-infobar was.

Co-authored-by: omp <[email protected]>
README gets an Installable app section: what the worker caches and why a signature can never be
cached, the install/update flow, and the hosting facts a self-hoster needs -- service workers
require HTTPS except on localhost (a Cloudflare Tunnel already gives it), .webmanifest needs
application/manifest+json, subpath deployments, and the rsvg-convert command that regenerates the
icon set from the committed SVGs.

Caddyfile sets that content type: Go's mime table (verified with mime.TypeByExtension) has no
.webmanifest entry, so Caddy would serve the manifest as octet-stream.

Co-authored-by: omp <[email protected]>
clients.claim() makes the initial install fire controllerchange on an
uncontrolled page too; reloading there wiped whatever a new visitor
typed seconds after arrival. Only a takeover under an existing
controller reloads, once.
EduarD3V closed this pull request 2026-10-15 08:46:37 +02:00

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
EduarD3V/AutoFirme!51
No description provided.