diff --git a/branding/hyperhive-maskable.svg b/branding/hyperhive-maskable.svg deleted file mode 100644 index 699f4ab6..00000000 --- a/branding/hyperhive-maskable.svg +++ /dev/null @@ -1,13 +0,0 @@ - - HyperHive (maskable) - Padded variant of hyperhive.svg for Android adaptive/maskable icon masks — - the OS crops a maskable icon to an arbitrary shape (circle, squircle, ...) and - only guarantees the inner ~80% "safe zone" survives; hyperhive.svg's own - artwork already fills nearly its whole canvas (a ~5% margin, meant for a - plain square/rounded-square favicon, not an aggressive crop), so used - directly here it would lose its outer ring and corner brackets on a circular - mask. This wrapper embeds the original at 80% scale, centered, over a - matching solid background instead of duplicating its markup. - - - diff --git a/frontend/packages/swarm-ui/build.mjs b/frontend/packages/swarm-ui/build.mjs index 41f8a1c1..1a7d0c40 100644 --- a/frontend/packages/swarm-ui/build.mjs +++ b/frontend/packages/swarm-ui/build.mjs @@ -16,17 +16,6 @@ // 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 @@ -73,7 +62,5 @@ 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("")); diff --git a/frontend/packages/swarm-ui/src/index.html b/frontend/packages/swarm-ui/src/index.html index dfd9d672..0959cfea 100644 --- a/frontend/packages/swarm-ui/src/index.html +++ b/frontend/packages/swarm-ui/src/index.html @@ -5,19 +5,6 @@ hyperhive swarm - - - - - - - diff --git a/frontend/packages/swarm-ui/src/main.tsx b/frontend/packages/swarm-ui/src/main.tsx index 6294fc42..5068ce59 100644 --- a/frontend/packages/swarm-ui/src/main.tsx +++ b/frontend/packages/swarm-ui/src/main.tsx @@ -5,17 +5,3 @@ const root = document.getElementById("root"); if (root) { render(, root); } - -// PWA installability — see sw.js's own top comment for -// what it does and does not cache. Feature-detected: older browsers -// without service-worker support just don't get the install affordance, -// nothing here depends on it existing. -if ("serviceWorker" in navigator) { - window.addEventListener("load", () => { - navigator.serviceWorker.register("/sw.js").catch((err: unknown) => { - // Never surfaces to the operator — a failed SW registration means - // "not installable this session," not "the app is broken." - console.error("swarm-ui: service worker registration failed", err); - }); - }); -} diff --git a/frontend/packages/swarm-ui/src/manifest.webmanifest b/frontend/packages/swarm-ui/src/manifest.webmanifest deleted file mode 100644 index d62d6ecc..00000000 --- a/frontend/packages/swarm-ui/src/manifest.webmanifest +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "hyperhive", - "short_name": "swarm", - "description": "Swarm-level operator UI — agent roster, hives, jobs, issues across the swarm.", - "start_url": "/", - "display": "standalone", - "background_color": "#1e1e2e", - "theme_color": "#cba6f7", - "icons": [ - { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" }, - { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }, - { "src": "/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } - ] -} diff --git a/frontend/packages/swarm-ui/src/sw.js b/frontend/packages/swarm-ui/src/sw.js deleted file mode 100644 index a7b80c55..00000000 --- a/frontend/packages/swarm-ui/src/sw.js +++ /dev/null @@ -1,117 +0,0 @@ -// Minimal service worker — satisfies PWA installability so swarm-ui can -// actually be "Add to Home Screen"d (design-guide.md's "Layout & -// viewport" section already asserted this as fact before this service -// worker existed to make it true). NOT a data cache: scoped to the app -// shell only (the SPA's own HTML/JS/CSS/manifest), and explicitly never -// touches `/api/*` — an ops dashboard silently showing stale status from -// a cached fetch would be worse than showing nothing (mara: "scope looks -// good" — approving that as a hard rule, not a short-TTL middle ground). -// -// Network-first for the shell, not cache-first: `main.js`/`main.css` are -// unhashed filenames, so a cache-first strategy would risk wedging an -// operator on yesterday's JS after a deploy until they manually cleared -// it (a deploy changes the nix store path serving these but not the URL -// the browser cached against — a separate, already-filed fix). This SW -// only ever serves its cache on a FAILED fetch (offline/flaky network), -// never in preference to a successful network response. -// -// Plain JS, not TypeScript, deliberately — the DOM lib swarm-ui's own -// tsconfig uses and the WebWorker lib a service worker's globals -// (`self`, `ExtendableEvent`, `FetchEvent`, `caches`, ...) need are -// mutually exclusive in one tsc program, and this file is small/ -// self-contained enough that a second tsconfig just to typecheck it -// isn't worth the config surface. Same treatment `build.mjs` already -// gets in this package. -// -// CACHE_VERSION is bumped by hand for now — once the filenames above are -// content-hashed, the cache name can derive from the build itself and -// this manual step goes away. -const CACHE_VERSION = "v1"; -const CACHE_NAME = `swarm-ui-shell-${CACHE_VERSION}`; - -// Every navigation (any client-side route wouter handles — /agents, -// /hives, ...) falls back to this single cached document when offline, -// same as nginx's own `try_files $uri /index.html` does online: the SPA -// only ever has one real HTML document regardless of path. -const SHELL_DOCUMENT = "/"; -const SHELL_ASSETS = [ - SHELL_DOCUMENT, - "/static/main.js", - "/static/main.css", - "/static/colors.css", - "/static/theme.css", - "/static/swarm-ui.css", - "/manifest.webmanifest", -]; - -self.addEventListener("install", (event) => { - event.waitUntil( - caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_ASSETS)), - ); - // Take over from any previous SW as soon as this one's installed — an - // operator installing the PWA for the first time (or after a SW - // update) shouldn't need a second reload before it's in control. - self.skipWaiting(); -}); - -self.addEventListener("activate", (event) => { - event.waitUntil( - caches - .keys() - .then((names) => - Promise.all( - names - .filter((name) => name !== CACHE_NAME) - .map((name) => caches.delete(name)), - ), - ) - .then(() => self.clients.claim()), - ); -}); - -self.addEventListener("fetch", (event) => { - const url = new URL(event.request.url); - // Same-origin only, and never `/api/*` — that's swarm-controller's - // live data, not the static shell this SW is scoped to. Letting the - // event fall through (no `respondWith`) means the browser handles it - // exactly as if this SW didn't exist. - if (url.origin !== self.location.origin || url.pathname.startsWith("/api/")) { - return; - } - - // Any navigation (full-page load or wouter's client-side route change - // reaching the network) — network-first, falling back to the cached - // shell document on failure regardless of which path was requested, - // since every route renders the same SPA shell. - if (event.request.mode === "navigate") { - event.respondWith( - fetch(event.request).catch(() => caches.match(SHELL_DOCUMENT)), - ); - return; - } - - // A specific shell asset — network-first, cache the fresh response for - // next time, fall back to whatever's cached on failure. Anything not - // in this list (a future asset this SW doesn't know about yet) passes - // straight through untouched rather than silently going uncached. - if (!SHELL_ASSETS.includes(url.pathname)) { - return; - } - event.respondWith( - fetch(event.request) - .then((response) => { - // Only a genuinely good response is worth caching — an error - // response (a 404/500, or a proxy hiccup wearing a 2xx-adjacent - // status) would otherwise get served back as if it were the real - // asset on the next offline/failed fetch. - if (response.ok) { - const copy = response.clone(); - caches - .open(CACHE_NAME) - .then((cache) => cache.put(event.request, copy)); - } - return response; - }) - .catch(() => caches.match(event.request)), - ); -}); diff --git a/nix/packages/default.nix b/nix/packages/default.nix index c0a534a8..38d41522 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -197,7 +197,6 @@ in # `packages.default`'s closure. swarm-ui = pkgs.callPackage ./swarm-ui.nix { branding-svg = ../../branding/hyperhive.svg; - branding-svg-maskable = ../../branding/hyperhive-maskable.svg; }; # Bundled browser assets — see ./frontend.nix. Output is diff --git a/nix/packages/swarm-ui.nix b/nix/packages/swarm-ui.nix index 6d1e5d52..035dc904 100644 --- a/nix/packages/swarm-ui.nix +++ b/nix/packages/swarm-ui.nix @@ -1,9 +1,4 @@ -{ - buildNpmPackage, - branding-svg, - branding-svg-maskable, - librsvg, -}: +{ buildNpmPackage, branding-svg }: # Static build of the swarm-level UI shell (project-bootstrap scope: # empty start page, no functionality yet — see @@ -36,14 +31,6 @@ # `./frontend.nix` copies for the dashboard) is likewise copied in # during install — it lives outside the npm tree. -# PWA icons: rasterized from the branding SVGs at build time via -# `librsvg`'s `rsvg-convert` rather than checking in static PNGs, so the -# SVG stays the one thing to update if the mark ever changes. -# `branding-svg-maskable` is a separate, padded source (see -# `branding/hyperhive-maskable.svg`'s own comment) — `hyperhive.svg`'s -# artwork already fills nearly its whole canvas, so a maskable icon needs -# real margin or an OS mask crops into it. - buildNpmPackage { pname = "hyperhive-swarm-ui"; version = "0.0.0"; @@ -52,8 +39,6 @@ buildNpmPackage { # See `./frontend.nix`'s comment on the same line. npmDepsHash = builtins.readFile ../../frontend/npm-deps-hash; - nativeBuildInputs = [ librsvg ]; - buildPhase = '' runHook preBuild npm run build --workspace=packages/swarm-ui @@ -66,18 +51,6 @@ buildNpmPackage { mkdir -p $out cp -r packages/swarm-ui/dist/. $out/ cp ${branding-svg} $out/favicon.svg - rsvg-convert -w 192 -h 192 ${branding-svg} -o $out/icon-192.png - rsvg-convert -w 512 -h 512 ${branding-svg} -o $out/icon-512.png - - # `branding-svg-maskable` embeds `branding-svg` via a relative - # `` reference (see that file's own comment) — each is passed - # in as its own single-file store path, so the sibling reference - # only resolves once both live together in one directory again. - mkdir -p "$TMPDIR/branding" - cp ${branding-svg} "$TMPDIR/branding/hyperhive.svg" - cp ${branding-svg-maskable} "$TMPDIR/branding/hyperhive-maskable.svg" - rsvg-convert -w 512 -h 512 "$TMPDIR/branding/hyperhive-maskable.svg" \ - -o $out/icon-512-maskable.png runHook postInstall '';