Files
ducoterra dbf2af26c6 refactor(agents): migrate .agent/ planning tree to .agents/
Standardize on the .agents/ directory (shared with project skills):
phases/, user_stories/, reports/, screenshots/, validate.sh, and
phase-sessions/ + pipeline.log all move to .agents/ (git mv preserves
history; runtime artifacts move alongside).

Updates every reference in AGENTS.md, README.md, .gitignore, app
docstrings, and test story headers. Historical KB content in data/
and the runtime pipeline.log transcript are left untouched.
2026-09-05 10:57:07 -04:00

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

  1. frontend/assets/themes/indigo.css (NEW) — overrides ONLY the 8 identity variables in a single :root block; 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.css should match ONLY the :root palette block + comments (if a literal lives elsewhere, note it in the task report — do NOT refactor styles.css in this phase).
  2. frontend/assets/themes/README.md (NEW) — the authoring guide, house-comment style:
    • How themes load: BOR_THEME=<file> → /api/config → brand.js inserts <link rel="stylesheet" href="/assets/themes/<file>"> AFTER styles.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 :root block; 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 -r picks up whatever is in frontend/assets/themes/ at build time — no Containerfile edit for new files, A7).
  3. Containerfile — stage-1 RUN chain: after the styles.css minify line, add
    && cp -r ./assets/themes /out/assets/themes \
    
    (A7: whole directory, no per-file esbuild — future themes need no Containerfile change). No other stage changes; the runtime stage copies the whole /out static tree, so the dir flows through untouched.
  4. Dev-server sanity (record in task report): with BOR_THEME=indigo.css, http://localhost:8000/assets/themes/indigo.css returns 200 from the static dir; with the default env the built-in palette renders (the file existing but BOR_THEME empty changes NOTHING — loading is opt-in via the env var, not directory scanning).

Testing & Quality

  • No Python logic; uv run pytest green; uv run ruff check . && uv run pyright clean.
  • Coverage: >90% on app/ (validate.sh gate).
  • Add the theme-file unit pins to tests/unit/test_frontend_brand.py (or test_stale_ui_copy.py's sibling if the executor prefers a new tests/unit/test_themes.py): the file exists, starts with a :root block, contains exactly the 8 -- overrides (no other declarations), and the Containerfile contains the cp -r ./assets/themes line.
  • Browser proof (computed --brand, the link tag, visual sanity) lands in task 05's E2E.

Completion Criteria

  • frontend/assets/themes/indigo.css + README.md exist; all five contrast ratios verified ≥4.5:1 (calc output in the task report).
  • Containerfile ships assets/themes/ (the line is in stage 1, after the styles.css minify).
  • The grep spot-check for hard-coded identity colors is recorded (expect: :root block + comments only).
  • Unit pins green; full suite green; lint + types clean.