berrypod/docs/plans/admin-font-loading.md
jamey e5362d56fc add admin font loading plan doc
Documents the missing @font-face and font-family on admin/setup
pages, the cache miss path resolver bug, and options for fixing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 17:14:01 +00:00

4.0 KiB

Admin font loading

Status: Open — needs discussion

Problem

Custom fonts don't load on admin or setup pages. The setup page at /setup renders in the browser's default serif font on production.

Root cause

The admin CSS (admin.css) has no @font-face declarations and no font-family on body/root. Font family CSS variables are defined in theme-primitives.css (e.g. --p-font-inter: 'Inter', system-ui, sans-serif) but the actual woff2 font files are only loaded via @font-face rules generated at runtime by CSSGenerator for shop pages (injected into <style> tags via ThemeHook / LoadTheme plug).

Admin and setup pages use the root.html.heex layout which loads admin.css only — no generated theme CSS, no @font-face declarations.

Why it works locally

If you have Inter or other theme fonts installed on your system, the browser resolves the font-family names without needing @font-face. On production or other machines without the fonts installed, it falls through to browser defaults (usually serif).

Secondary bug: cache miss path

Three places regenerate CSS on ETS cache miss without the path_resolver for digested font URLs. This means if the cache is cold (after invalidation, restart race, etc.), the cached CSS contains bare paths like /fonts/inter-v20-latin-regular.woff2 which 404 in production (only digested paths are served).

Affected files:

  • lib/berrypod_web/plugs/load_theme.ex:30CSSGenerator.generate(settings) (no resolver)
  • lib/berrypod_web/theme_hook.ex:29 — same
  • lib/berrypod_web/controllers/error_html.ex:117 — same

CSSCache.warm/0 does it correctly: CSSGenerator.generate(settings, &BerrypodWeb.Endpoint.static_path/1)

Options

Option A: Quick fix — system font stack for admin

Add font-family: system-ui, -apple-system, sans-serif to the admin reset CSS. Admin pages get a clean sans-serif font without loading any custom font files.

Pros: zero extra bytes, fast, no font loading flash Cons: admin and shop have completely different typographic feel

Option B: Load Inter for admin pages

Add static @font-face declarations for Inter (the default body font) to admin.css. Admin gets a consistent font with minimal overhead (~50KB for regular + medium weights).

Pros: consistent typography across admin/shop Cons: extra font loading, static declarations need updating if font files change

Option C: Shared font loading infrastructure

Rethink the split between admin and shop CSS. Currently they're two completely separate stacks:

  • Shop: runtime-generated CSS with @font-face via CSSGenerator, theme tokens, semantic layer
  • Admin: hand-written CSS with DaisyUI-derived colour themes, no font loading

Could unify font loading so both share the same @font-face declarations (either static in a shared CSS file, or via a shared plug/hook). The theme font primitives are already in theme-primitives.css which both stacks import.

This is a bigger piece of work and might be overengineering for the current state — the admin and shop serve very different purposes and their CSS stacks reflect that.

Option D: Full admin CSS rework

Rebuild admin CSS to share more with the shop CSS architecture. Both would use the three-layer system (primitives, attributes, semantic) with the admin getting its own semantic layer but sharing primitives and font loading.

This is the most ambitious option and would need its own plan. Worth considering if the admin UI is going to grow significantly, but could be premature for MVP.

Recommendation

Fix the cache miss bug regardless (Option A baseline). For fonts specifically, Option A or B are both reasonable quick wins. Option C/D are worth revisiting when the admin UI needs a bigger rework.

  • Cache miss bug affects shop pages too (not just admin)
  • CSSCache.warm/0 runs on startup so the bug only manifests on race conditions or manual invalidation
  • The @font-face declarations in generated CSS use bare paths without path_resolver, which 404s in production