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 @@
-
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
'';