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

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<!-- ApiCore mark (spec 12.1): a capped honeycomb cell. Master for scripts/generate-icons.mjs;
the geometry equals BRAND_CELL_PATHS in src/components/brand-mark.tsx. -->
<path id="cell" d="M32 4 L56 18 V46 L32 60 L8 46 V18 Z" fill="#f0b13f" stroke="#2c1b0f" stroke-width="3.84" stroke-linejoin="round" />
<path id="lid" d="M32 13.6 L45.2 21.3 V36.7 L32 44.4 L18.8 36.7 V21.3 Z" fill="#f9d280" />
</svg>

After

Width:  |  Height:  |  Size: 477 B

+1 -8
View File
@@ -1,8 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" rx="96" fill="#0a0a0a" />
<g fill="#f5c518">
<rect x="156" y="176" width="200" height="52" rx="10" />
<rect x="156" y="242" width="200" height="52" rx="10" />
<rect x="156" y="308" width="200" height="52" rx="10" />
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect width="512" height="512" rx="112.64" fill="#2c1b0f"/><g transform="translate(51.2 51.2) scale(6.4)"><path d="M32 4 L56 18 V46 L32 60 L8 46 V18 Z" fill="#f0b13f" stroke="#2c1b0f" stroke-width="3.84" stroke-linejoin="round" /><path d="M32 13.6 L45.2 21.3 V36.7 L32 44.4 L18.8 36.7 V21.3 Z" fill="#f9d280" /></g></svg>

Before

Width:  |  Height:  |  Size: 340 B

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

+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)) {