Self-hosted Google Fonts with per-pond selection (ADR 0016), the GDPR
"zero external requests" posture (security.md, CSP `font-src 'self'`).
- Catalog: a curated 15-family OFL/Apache list in shared (family, weights,
category, license, google-webfonts-helper id). `deploy/fonts/build-fonts.mjs`
validates every entry has license info (fails the build otherwise),
downloads the WOFF2 weights into apps/web/public/fonts/ (gitignored), and
generates the @font-face stylesheet — run at image build time from the web
Dockerfile (with retries), never from a visitor's browser.
- Application: PondFontScope sets --font-heading/body/mono (+ weights) from
pond.settings.fonts on the editor + read view; the existing global CSS
already reads those custom properties, so headings/body/code re-resolve to
the pond's fonts. A pond with no settings arrives with the defaulted values
(Roboto 400 / Roboto 200 / Fira Code), so the vision defaults always render.
- Admin UI: pond-settings 'Appearance' section — three slots (family + weight)
with a live preview, Pond-Admin-gated (fonts added to updatePondInputSchema
and merged in PondsService.update); a font catalog attribution page (/fonts)
listing families and licenses. New `font` i18n namespace (de+en).
- CSP: strict Content-Security-Policy in nginx.conf (default-src 'self';
font-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; …) —
the app's scripts are all external files, inline styles cover CSS variables.
- Tests: shared catalog-integrity unit test (the invariant the build enforces);
e2e fonts pack — no request leaves the origin when rendering a pond (the GDPR
network assertion), a font choice applies to a page and persists, and a pond
without settings renders the defaults.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
apps/web becomes a Vite + React application: React Router with home
and 404 routes, base layout (top bar, collapsible sidebar remembered
per user via localStorage, main area), CSS design tokens including the
three font slots from ADR 0016, TanStack Query, and a typed fetch
helper showing live API health on the home page. All UI strings go
through a t() stub that issue #5 replaces with i18next. The Vite dev
server proxies /api to the api dev port (3001).
Closes#4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>