All checks were successful
whatever the operator called their instance, `instance.name` was never rendered in the running app at all, and there was no favicon anywhere — `index.html` had no `<link rel="icon">` and `public/` held only fonts and theme-init.js. Where the line is drawn, and why: - **The api never decodes an image.** Cropping, scaling and the conversion to PNG happen on a canvas in the browser; the api checks the PNG signature, reads the IHDR dimensions at their fixed offsets and enforces the caps. An image library would put a decoder in front of attacker-supplied bytes AND would have to be carried through the `--network none` offline build. Reading two big-endian integers is not decoding. - **SVG is refused**, with its own error message rather than a generic "not a PNG": it can carry script, and serving it from our own origin would be a cross-site-scripting vector. An operator who tried one should learn that it is deliberate. - **The crop is driven by number inputs, not by dragging.** A drag-only cropper excludes keyboard and switch users outright; a number input is arrow-key operable and screen-reader readable without any custom aria. The resulting pixel size is stated in text, not only drawn as a frame. - **The variant is chosen by CSS, not JavaScript.** `theme-init.js` has already resolved `data-theme` before first paint, so the correct logo is the one painted rather than the one that appears after a flash. Without a dark variant the LIGHT logo carries both themes — the operator's own asset shown unchanged beats one they did not choose (the rule #307 extends to ponds). The settings screen warns; it never blocks. - **The favicon link is static, its resource dynamic.** index.html stays a static file and the api answers with the uploaded icon or a shipped default — that route must never 404, or the browser keeps its generic icon for good. The default is generated by a script from Node's own zlib (`gen-default-favicon.mjs`), for the same offline-build reason. - Both favicon sizes are uploaded together: one source, one crop, so the tab icon and the home-screen icon can never disagree. - Branding is served WITHOUT a session, because the login screen carries it and the browser fetches the favicon before anyone signs in. The admin screen says so — an operator may not expect their logo to be public. - The metadata is not writable through the settings endpoint: it describes bytes on disk, and hand-writing it would claim an asset that is not there. `./data/branding` follows the three-step rule #303 paid for: env default + `data-dirs.ts` entry, compose volume (repo AND the stages on ONE), and the `mkdir`/`chown` line in the api Dockerfile. `data-dirs.test.ts` is new and closes the hole that made #303's variant invisible: the nightly archive skips a missing directory WORDLESSLY, so the fence now demands that every `*_DIR` the backup env declares actually travels in the archive. Verified against the real defect — removing the line fails it by name. Audit catalogue v1.7 (`branding.changed`), carrying `scope` from the start so #307 is the same event with a different scope, not a second id. Verified: api suite 103 files green (a lone `public-api` ECONNRESET under local parallel load, green in isolation — the documented local flake); branding suite 12 tests against a real directory; crop arithmetic unit tests; a11y pack 11/11 in both schemes; /admin measured at 320px with the new section (overflow 0); and the whole flow walked in the browser: upload → crop 780×180 → stored as 512×118 → logo in the sidebar linking home with the instance name as its accessible name → topbar wordmark following `instance.name` → light logo still shown under `data-theme="dark"`.
34 lines
1.7 KiB
HTML
34 lines
1.7 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<!-- Applies at parse time, before any script or stylesheet: the UA
|
|
canvas is already dark for system-dark users, killing the white
|
|
flash. theme-init.js then narrows it to the resolved theme. -->
|
|
<meta name="color-scheme" content="light dark" />
|
|
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#2f6f4f" />
|
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#10161d" />
|
|
<title>Dorfteich</title>
|
|
<!-- Static link, dynamic resource (issue #306): the api answers with the
|
|
operator's favicon or the shipped default, so this href never has to
|
|
change and index.html stays a static file. An attribute like `lang`
|
|
cannot be indirected this way — that is #179's problem, not this
|
|
one's. -->
|
|
<link rel="icon" type="image/png" href="/api/v1/branding/favicon" />
|
|
<link rel="apple-touch-icon" href="/api/v1/branding/favicon?size=180" />
|
|
<!-- Classic (non-module) script: executes during head parsing, before
|
|
first paint and before the deferred module bundle. External file
|
|
because the prod CSP forbids inline scripts (issue #180). -->
|
|
<script src="/theme-init.js"></script>
|
|
<!-- Self-hosted catalog @font-face rules (ADR 0016), baked into the image
|
|
by deploy/fonts/build-fonts.mjs. Absent in a plain dev build → the app
|
|
falls back to system fonts; never references a third-party origin. -->
|
|
<link rel="stylesheet" href="/fonts/catalog.css" />
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
<script type="module" src="/src/main.tsx"></script>
|
|
</body>
|
|
</html>
|