phase: 91_admin_theme_tab
Build and Push Containers / build-and-push-app (push) Successful in 5m43s
Build and Push Containers / build-and-push-db (push) Successful in 12s

All verification is complete — this pass needed no code changes. Final report:

**Phase 91 — Admin Theme tab: final verification pass (all 6 tasks already in `complete/`)**

- Verified pre-paint theming end-to-end: `ui_settings` store + resolver, admin `GET/PUT /api/ui-settings`, `CachingMiddleware` inline-`<style id="bor-theme">` injection before `</head>` (incl. `/shared/<token>` prefix branch, unit-pinned), CSP sha256 exemption for the inline tag, Theme tab shell + `theme.js` editor, CSS-file theming fully retired.
- No defects found; zero changes made — working tree left exactly as the task executors left it.
- Tests: `uv run pytest --cov=app` → 1841 passed, 0 failed (TOTAL coverage **99%**; theming/ui_settings/caching all 100%); `uv run pytest tests/e2e/test_admin_theme_tab.py -v --no-cov` → **5 passed** in isolation.
- Lint/types: `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings.
- Criteria: (1) unset deployment byte-identical, no `#bor-theme` anywhere — ✓ (unit no-op test + E2E reset byte-compare); `rg "BOR_THEME|themes/"` → single hit is the permitted doc-history comment in `frontend/index.html`. (2) admin-only gate + 403s for anonymous and token users — ✓ (E2E test 3). (3) saved theme inline before `</head>` on every page incl. `/shared/<token>`, computed `--brand` on first paint for admin + anonymous — ✓ (E2E test 2 + unit). (4) reset → byte-identical; 5 contrast pairs warn <4.5:1, non-blocking — ✓ (E2E tests 4–5). (5) suite green, >90% coverage, lint clean — ✓. (6) commit deferred to harness per rules.
- Notable: `.agents/PLAN.md` is absent from the repo — the phase overview's Design section was used as the binding spec; no deviation resulted.
- Next pending phase: **none** — 91 is the last phase in `todo/`.
This commit is contained in:
2026-09-09 17:22:24 -04:00
parent 3095c4c577
commit d22d260b8b
74 changed files with 4448 additions and 675 deletions
+53
View File
@@ -812,3 +812,56 @@ class TokenAuthRequest(BaseModel):
"""
token: str
class UiSettingsIn(BaseModel):
"""``PUT /api/ui-settings`` body (phase 91, task 01): a FULL
replacement of the single ``ui_settings`` row.
Every field is ``str | None`` — present = a new value (strings are
trimmed; empty after the trim is the CLEAR operation, stored as
NULL; colors must be ``#rrggbb`` and are lowercased on store),
``null``/absent = "back to the default" (stored as NULL — the Reset
button's all-null PUT is exactly the "defaults" operation). The
API layer runs the trim/length/hex validation so the 422 details
name the offending field (the house fixed-detail style); the
built-in→NULL normalization (a color equal to its built-in is
stored as NULL — "save the defaults" must leave the row empty, the
no-op injection contract) happens there too, next to the palette
it normalizes against.
"""
app_name: str | None = None
input_placeholder: str | None = None
footer_text: str | None = None
bg: str | None = None
surface: str | None = None
ink: str | None = None
ink_soft: str | None = None
line: str | None = None
brand: str | None = None
brand_soft: str | None = None
brand_ink: str | None = None
class UiSettingsOut(BaseModel):
"""Effective UI settings (``GET``/``PUT /api/ui-settings`` response,
phase 91, task 01).
All 11 values, all non-null strings: the resolver's
DB-over-env / DB-over-built-in merge (B1), so the tab always shows
the LIVE theme — a fresh (row-missing) deployment reports the env
strings and the built-in palette.
"""
app_name: str
input_placeholder: str
footer_text: str
bg: str
surface: str
ink: str
ink_soft: str
line: str
brand: str
brand_soft: str
brand_ink: str