## Mærket Urds oplagte mærke var ᚢ uruz — bogstavet navnet begynder med i den ældre futhark, præcis som Verdandes er ᚹ wunjo. Det blev tegnet sådan først. Men uruz er allerede et af husets egne produkter, og dets egen `scripts/gen-icons.ts` siger i sin kommentar at den "draws the Uruz rune (ᚢ) in rune-gold" med "a tall left vertical, a shoulder across the top, and a shorter" — altså samme glyf, samme konstruktion, kun en anden farve. To af samme persons apps side om side på en hjemmeskærm, hvilket er nøjagtig det sted mærket skal kunne skelnes. ᚦ thurisaz blev forkastet et niveau nede af samme grund: en stav med en trekant til højre, og det er ᚹ også. Ved 16px er de én form. Så bogstavet frem for runen: ð, ethet i Urðr. Centreret ved at tælle malede pixels frem for ud fra path-tallene, fordi en centreret kasse ikke er en centreret glyf: verdande ᚹ blæk x 53–108 midte 80.5 · y 18–131 midte 74.5 urd ð blæk x 39–110 midte 74.5 · y 15–134 midte 74.5 Verdandes +5.5 i x er dens egen bevidste forskydning — en tung stav ved siden af et let flag skal sidde til højre for midten for at se centreret ud. ð er symmetrisk nok til at undvære den, og de to er enige i y. Læsbarhed, målt: ð læses fra 32px og op. Ved 16px er det en klat, og det er ᚹ også — en favicon er ikke det, der skiller to installerede apps i en dock. `productRune` hedder nu `productMark`, fordi ð ikke er en rune. ## Determinisme SvelteKits `kit.version.name` er som standard `Date.now()`. Millisekundet landede i version.json, i index.html og i service-workeren, ændrede indholdshashen på hver chunk der refererer dem, og dermed deres filnavne. To bygninger af samme commit havde forskellige filer i sig. Dockerfilen har i et år sagt at `-trimpath` og en tom buildid "keep the output reproducible". Flagene gjorde deres arbejde; frontenden i binæren bar et tidsstempel. Sætningen var falsk, og ingen læste den. Fastgjort til VERSION, som udgivelsen allerede sender ind, og `dev` når intet sætter den. Målt ved at bygge to gange og diffe træet, før og efter. Prisen er at et lokalt `npm run build` ikke længere ligner en ny version for service-workeren — hvilket kun betyder noget på en udrullet instans, hvor udgivelsen sender det rigtige mærkat. Det gør også CI's nye påstand mulig: de to images deler alle lag, så urd bærer samme binær som verdande frem for en genbygning der tilfældigvis matcher. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
2.6 KiB
JavaScript
72 lines
2.6 KiB
JavaScript
/**
|
||
* Renders each edition's icon SVG to the PNGs the manifest and iOS need.
|
||
*
|
||
* node scripts/generate-icons.js
|
||
*
|
||
* A script rather than a test, so CI never runs it and never rewrites a tracked
|
||
* binary. Run it when an icon changes; the smoke test fetches every icon the
|
||
* manifest names, so a reference with no file behind it fails there.
|
||
*
|
||
* It drives Chromium because there is no SVG renderer on this machine — which is
|
||
* why these references were once removed rather than left pointing at files that
|
||
* did not exist. Playwright is a renderer, and it is already a dependency.
|
||
*
|
||
* Two sets, because there are two editions. Both ship in the one binary and the
|
||
* server decides which to serve at /icon.svg and friends — see
|
||
* internal/httpapi/shell.go. The alternative, rewriting the manifest to point at
|
||
* urd-icon-192.png, would have left `apple-touch-icon.png` behind: Safari looks
|
||
* for that one BY NAME and ignores the manifest entirely, so a renamed path there
|
||
* is a home-screen icon that is a screenshot of the page.
|
||
*/
|
||
import { chromium } from '@playwright/test';
|
||
import { readFileSync, writeFileSync } from 'node:fs';
|
||
|
||
// The three sizes, and why each is here.
|
||
const SIZES = [
|
||
// The two the manifest asks for: 192 for a launcher, 512 for a splash screen.
|
||
{ suffix: '-192.png', size: 192 },
|
||
{ suffix: '-512.png', size: 512 },
|
||
// And the one Safari wants by name.
|
||
{ suffix: '-apple-touch.png', size: 180 }
|
||
];
|
||
|
||
// `out` is spelled out per set rather than built from the prefix, because
|
||
// verdande's three were named before there was a second set and renaming them
|
||
// would move the paths the manifest and app.html already point at.
|
||
const SETS = [
|
||
{
|
||
source: 'static/icon.svg',
|
||
out: {
|
||
'-192.png': 'static/icon-192.png',
|
||
'-512.png': 'static/icon-512.png',
|
||
'-apple-touch.png': 'static/apple-touch-icon.png'
|
||
}
|
||
},
|
||
{
|
||
source: 'static/urd-icon.svg',
|
||
out: {
|
||
'-192.png': 'static/urd-icon-192.png',
|
||
'-512.png': 'static/urd-icon-512.png',
|
||
'-apple-touch.png': 'static/urd-apple-touch-icon.png'
|
||
}
|
||
}
|
||
];
|
||
|
||
const browser = await chromium.launch();
|
||
const page = await browser.newPage();
|
||
|
||
for (const set of SETS) {
|
||
const svg = readFileSync(set.source, 'utf8');
|
||
for (const { suffix, size } of SIZES) {
|
||
const file = set.out[suffix];
|
||
await page.setViewportSize({ width: size, height: size });
|
||
await page.setContent(
|
||
`<style>html,body{margin:0;padding:0}svg{display:block;width:${size}px;height:${size}px}</style>${svg}`
|
||
);
|
||
writeFileSync(file, await page.locator('svg').screenshot());
|
||
console.log(`${file} ${size}×${size}`);
|
||
}
|
||
}
|
||
|
||
await browser.close();
|