feat(t5): App-Icons, Manifest und Offline-Cache v5 (T5a A9)

- Honigzelle auf Tinte als Master-SVG; scripts/generate-icons.mjs erzeugt
  any/maskable 192/512, Monochrom 512, drei Shortcut-Icons, Apple-Icon und
  favicon.ico (16/32/48, ICO-Container von Hand) byte-gleich bei jedem Lauf.
- manifest.ts exakt nach Spec 12.3, Farben gleich dem hellen --background.
- Service Worker: SHELL_CACHE apicore-shell-v5, Icons im Shell-Cache; ein
  fehlendes Icon hält das Vorladen der Seiten nicht auf.
- Proxy-Matcher: nur echte Icon-Dateien (icons/*.png) ohne
  Sicherheits-Header, Punkte maskiert.
- BrandMark-Deckel mittig (linke Kante 18.8).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick.RosenloecherandClaude Opus 5.5 committed 2026-10-16 17:51:41 +02:00
1 parent fee84c7a6b
commit 2395576c88
23 files changed
+632 -67

No files matched your search

+34 -1
View File
@@ -37,11 +37,28 @@ const {
buildAssetsToPrune,
} = self.ApiCoreSwRoutes;
const SHELL_CACHE = "apicore-shell-v4";
// v5: new app icons and colour tokens (T5 spec 12.3) reach installed apps.
const SHELL_CACHE = "apicore-shell-v5";
// v2: v1 could hold answers this version never keeps (exports, downloads).
const DATA_CACHE = "apicore-data-v2";
const KNOWN_CACHES = [SHELL_CACHE, DATA_CACHE];
// The manifest icons (src/app/manifest.ts; sw.spec checks that every one of
// them is kept): kept with the shell, so an offline start and the shortcuts
// show the app's own icons. Not the favicon: Next links it as
// /favicon.ico?<hash>, so a cached /favicon.ico would never answer it.
const SHELL_ICONS = [
"/icon.svg",
"/icons/icon-192.png",
"/icons/icon-512.png",
"/icons/maskable-192.png",
"/icons/maskable-512.png",
"/icons/monochrome-512.png",
"/icons/shortcut-inspection-96.png",
"/icons/shortcut-varroa-96.png",
"/icons/shortcut-today-96.png",
];
// Same budget as every request of the app (lib/http/fetch-with-timeout.ts).
const ASSET_TIMEOUT_MS = 4000;
const RUNTIME_CONFIG_PATH = "/api/runtime-config";
@@ -453,6 +470,7 @@ async function warmShell() {
}
}
await keepRuntimeConfig(cache).catch(() => undefined);
await keepIcons(cache);
// Only with the full picture of the current build may old assets go.
if (allPagesKept) {
await pruneOldBuildAssets(cache, [...assets]);
@@ -497,6 +515,21 @@ async function keepAsset(cache, asset) {
await cache.put(asset, response);
}
/**
* Fetches the icons fresh on every warm-up (their URLs carry no build hash).
* A missing icon is skipped; the pages do not depend on it.
*/
async function keepIcons(cache) {
await Promise.allSettled(
SHELL_ICONS.map(async (icon) => {
const response = await fetch(icon, { cache: "no-cache" });
if (isGoodAnswer(response)) {
await cache.put(icon, response);
}
}),
);
}
async function keepRuntimeConfig(cache) {
const response = await fetch(RUNTIME_CONFIG_PATH, { cache: "no-cache" });
if (isGoodAnswer(response)) {