feat(urd): mærket bliver ð, og frontend-bygget bliver deterministisk

## 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>
This commit is contained in:
kristianwind
2026-10-07 10:10:30 +02:00
co-authored by Claude Opus 5
parent b451fd585d
commit ff988d26bb
17 changed files with 463 additions and 33 deletions
+29 -5
View File
@@ -141,11 +141,11 @@ test('fanen, mærket og manifestet siger alle urd', async ({ page }) => {
const mærke = page.getByRole('navigation', { name: 'Hovedmenu' }).locator('.brand');
await expect(mærke.locator('.name')).toHaveText('urd');
// ᚢ uruz, det bogstav navnet begynder med i den ældre futhark — og ikke ᚹ
// wunjo, som er Verdandes. Begge er navngivne værdier frem for "der er en
// rune": en tom span og den forkerte rune ser ens ud for en prøve, der kun
// spørger om der står noget.
await expect(mærke.locator('.rune')).toHaveText('ᚢ');
// ð, ikke ᚹ wunjo og ikke ᚢ uruz. Begge de to er navngivne værdier frem for
// "der står noget": en tom span, Verdandes mærke og uruz-appens ser ens ud for
// en prøve, der kun spørger, om feltet er udfyldt.
await expect(mærke.locator('.rune')).toHaveText('ð');
await expect(mærke.locator('.rune')).not.toHaveText('ᚢ');
const manifest = await (await page.request.get('/manifest.webmanifest')).json();
expect(manifest.name).toBe('urd');
@@ -246,3 +246,27 @@ test('ingen side kalder en rute, udgaven ikke har', async ({ page }) => {
// Tallet med på det grønne: elleve sider og et projekt, frem for "ingen fejl".
expect(trouble, `besøgte ${sider.length} sider`).toEqual([]);
});
test('ikonet er urds, hele vejen ud', async ({ page }) => {
// Målt på stregerne i SVG'en frem for på "der kom et billede": en tom
// respons, verdandes mærke og urds ser alle ens ud for en prøve, der kun
// spørger, om der står noget.
const svg = await (await page.request.get('/icon.svg')).text();
expect(svg, 'ð: skålen').toContain('cx="32" cy="42" r="12"');
expect(svg, 'ð: ascenderen').toContain('M44 36L27 10');
expect(svg, 'ð: tværstregen, som er det der skiller ð fra et d').toContain('M25 20h18');
expect(svg, 'og ikke ᚹ wunjos flag').not.toContain('M26 15l17');
// De tre PNG'er har ingen streger at læse, så de måles på at de er urds —
// sammenlignet med filen under sit eget navn, som ligger i samme byg.
for (const [sti, egen] of [
['/icon-192.png', '/urd-icon-192.png'],
['/icon-512.png', '/urd-icon-512.png'],
['/apple-touch-icon.png', '/urd-apple-touch-icon.png']
]) {
const a = await (await page.request.get(sti)).body();
const b = await (await page.request.get(egen)).body();
expect(a.length, `${sti} er tom`).toBeGreaterThan(1000);
expect(a.equals(b), `${sti} er ikke urds`).toBe(true);
}
});