4.6 KiB
4.6 KiB
Task 03 — Example theme (indigo.css) + authoring guide + Containerfile line
Phase: 62_ui_customization · Source: TODO.md:3 — "…custom color themes…"
Story: n/a (TODO-derived)
Objective
A working example theme a deployer can point BOR_THEME at out of the box — plus the documentation that makes "write my own theme" a 15-minute job, and the one Containerfile line that ships the directory in the container image (A7).
Work
frontend/assets/themes/indigo.css(NEW) — overrides ONLY the 8 identity variables in a single:rootblock; the semantic families (accent/ok/err) are deliberately UNTOUCHED (they encode states — deflection amber, success green, error red — and are already AA in the built-in theme; a theme that keeps them stays honest):/* Phase 62 example theme — dark indigo/slate. Overrides the :root identity palette from styles.css; every text/background pair meets WCAG 2.1 AA (>= 4.5:1): ink on bg 15.8:1 · ink on surface 14.6:1 · ink-soft on surface 8.2:1 dark bg ink on brand 6.4:1 · brand-ink on surface 11.9:1. Semantic families (accent/ok/err) inherit the built-in theme. */ :root { --bg: #0a0e1a; --surface: #111726; --ink: #e6e9f0; --ink-soft: #a8b0c8; --line: #232c44; --brand: #818cf8; --brand-soft: #1a1f38; --brand-ink: #c7d2fe; }- Before committing, re-verify the five ratios above (e.g. a 10-line python contrast calc against the built-in pairs' house comment style) — if any pair misses 4.5:1, adjust the value, not the bar (AGENTS.md rule 5).
- Spot-check that no component CSS hard-codes a color the theme should own:
grep -n "f43f5e\|#f0e6e6\|#0f0a0a" frontend/assets/styles.cssshould match ONLY the:rootpalette block + comments (if a literal lives elsewhere, note it in the task report — do NOT refactor styles.css in this phase).
frontend/assets/themes/README.md(NEW) — the authoring guide, house-comment style:- How themes load:
BOR_THEME=<file>→/api/config→brand.jsinserts<link rel="stylesheet" href="/assets/themes/<file>">AFTERstyles.css(later wins the cascade — that is the whole mechanism). - The variable table: the 8 identity variables (bg/surface/ink/ink-soft/line/brand/brand-soft/brand-ink) with the built-in values as reference, and the note that accent/ok/err are semantic and should stay.
- Rules: filename
^[a-z0-9_-]+\.css$(lowercase, bare filename — the server validator rejects anything else at startup); one:rootblock; every text/background pair ≥4.5:1 (AGENTS.md rule 5); never white-on-brand (the built-in's documented 3.7:1 trap) — dark bg ink on brand, as the built-in does. - Deployment: works in dev immediately (served from the static dir); in the container, rebuild the image (the
cp -rpicks up whatever is infrontend/assets/themes/at build time — no Containerfile edit for new files, A7).
- How themes load:
Containerfile— stage-1RUNchain: after thestyles.cssminify line, add(A7: whole directory, no per-file esbuild — future themes need no Containerfile change). No other stage changes; the runtime stage copies the whole&& cp -r ./assets/themes /out/assets/themes \/outstatic tree, so the dir flows through untouched.- Dev-server sanity (record in task report): with
BOR_THEME=indigo.css,http://localhost:8000/assets/themes/indigo.cssreturns 200 from the static dir; with the default env the built-in palette renders (the file existing butBOR_THEMEempty changes NOTHING — loading is opt-in via the env var, not directory scanning).
Testing & Quality
- No Python logic;
uv run pytestgreen;uv run ruff check . && uv run pyrightclean. - Coverage: >90% on
app/(validate.sh gate). - Add the theme-file unit pins to
tests/unit/test_frontend_brand.py(ortest_stale_ui_copy.py's sibling if the executor prefers a newtests/unit/test_themes.py): the file exists, starts with a:rootblock, contains exactly the 8--overrides (no other declarations), and the Containerfile contains thecp -r ./assets/themesline. - Browser proof (computed
--brand, the link tag, visual sanity) lands in task 05's E2E.
Completion Criteria
frontend/assets/themes/indigo.css+README.mdexist; all five contrast ratios verified ≥4.5:1 (calc output in the task report).Containerfileshipsassets/themes/(the line is in stage 1, after the styles.css minify).- The
grepspot-check for hard-coded identity colors is recorded (expect::rootblock + comments only). - Unit pins green; full suite green; lint + types clean.