swarm-ui: make it installable as a PWA

Closes #4282. mara: "scope looks good" — approving the plan posted
there (manifest + icons + minimal shell-only service worker + iOS meta
tags) and both explicit questions (network-first-with-offline-fallback,
never cache /api/*).

docs/web-ui/design-guide.md's "Layout & viewport" section already
asserted swarm-ui is installable as a PWA — this is what actually backs
it.

- manifest.webmanifest: name/icons/start_url/standalone display, theme
  #cba6f7 / background #1e1e2e matching the mocha --purple/--bg values.
- sw.js: plain JS, not TypeScript — the DOM lib swarm-ui's own tsconfig
  uses and the WebWorker lib a service worker's globals need are
  mutually exclusive in one tsc program, not worth a second tsconfig for
  a self-contained ~100-line file. Scoped to the app shell only, never
  touches /api/* at all, network-first with offline-fallback-to-cache
  (not cache-first) since main.js/main.css are unhashed filenames and a
  cache-first SW would risk wedging an operator on stale JS after a
  deploy.
- index.html: manifest link, theme-color meta, iOS
  apple-mobile-web-app-* tags (Safari ignores the manifest spec).
- main.tsx: feature-detected SW registration.
- branding/hyperhive-maskable.svg: hyperhive.svg's own artwork already
  fills nearly its whole canvas, so a maskable icon needs a padded
  variant or an OS mask crops the outer ring/corner brackets — embeds
  the original via a scaled <image> ref rather than duplicating markup.
- nix/packages/swarm-ui.nix: rasterizes icon-192/512/512-maskable.png
  from the branding SVGs at build time via librsvg, rather than
  checking in static PNGs.

Verified for real: typecheck+build green, and a real headless-chromium
tab driven over CDP confirms the service worker registers and becomes
the active controller, and a simulated-offline reload still serves the
full cached shell rather than a browser error page. nix build .#swarm-ui
also verified green, including the rasterized icon output.
This commit is contained in:
iris 2026-09-12 00:59:50 +02:00 committed by mara
commit 9ad700a1a1
8 changed files with 205 additions and 1 deletions

View file

@ -16,6 +16,17 @@
// dist/static/swarm-ui.css served at /static/swarm-ui.css — just the
// shared base reset now (@import resolved
// from @hive/shared), NOT component styles
// dist/manifest.webmanifest served at /manifest.webmanifest (PWA)
// dist/sw.js served at /sw.js (PWA) — plain JS, no
// esbuild pass needed (see its own
// top comment for why); copied as-is.
//
// PWA icons (icon-192.png/icon-512.png/icon-512-maskable.png) are NOT
// produced here — like favicon.svg, they're rasterized from
// branding/*.svg and copied in by nix/packages/swarm-ui.nix's
// installPhase, outside the npm tree this script builds. A plain `npm
// run build` here has no `/icon-*.png`, same as it's always had no
// `/favicon.svg`.
//
// Not yet wired into any Rust binary's `ServeDir` — swarm-controller
// only serves `/health` today (see swarm-controller/README.md); this
@ -62,5 +73,7 @@ for (const entry of ["colors.css", "theme.css", "swarm-ui.css"]) {
}
copyFileSync(src("index.html"), dist("index.html"));
copyFileSync(src("manifest.webmanifest"), dist("manifest.webmanifest"));
copyFileSync(src("sw.js"), dist("sw.js"));
console.log("swarm-ui build ok →", dist(""));