phase: 177_themed_favicon
Build and Push Containers / build-and-push-app (push) Successful in 1m49s
Build and Push Containers / build-and-push-db (push) Successful in 12s

Everything is verified. Final report:

## Phase 177 — Themed favicon: completion pass (all tasks pre-done, verified end-to-end)

**Verified (no code fixes needed — implementation matched the design D1–D4 exactly):**
- `theming.favicon_svg()` pure renderer + golden byte-identical pin; `GET/HEAD /favicon.svg` route (no-cache, `image/svg+xml`, built-in 200 fallback on DB blip, routes-first before static mount); 5 templates → bare `href="/favicon.svg"`, static file deleted (Containerfile cp removed); Theme-tab `data:` live preview with 4 restore points; JS↔server mirror pins.

**Tests / lint / coverage (all green):**
- `uv run pytest --cov=app --cov-report=term-missing` → 4161 passed, `app/` 99% (>90% gate)
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors
- `uv run pytest tests/e2e/test_themed_favicon.py -v --no-cov` (isolated) → 6 passed
- `test_theme_save_and_coverage.py` (92) + `test_theme_semantic_completion.py` (93), isolated → 3 + 8 passed

**Completion criteria:** all met — default golden bytes/headers; themed roles + `#bor-theme` non-regression; DB-failure 200 fallback; 5 bare links + `/assets/favicon.svg` 404 + browser rendering; live-preview data: repaint + Save/Reset/re-show restore; gates green; E2E suites green in isolation. Commit: harness commits atomically (overrides AGENTS rule 8 — no git commands run).

**Documentation:** README.md verified consistent in full (the phase's favicon paragraph is accurate; all commands, env vars, routes, ports, scripts cross-checked) — no changes needed. AGENTS.md verified consistent — no changes needed.

**Notable:** `image-1.png` (owner's TODO screenshot, referenced by phase 176's overview) sits untracked at the repo root — pre-existing, explicitly out of scope per the phase-176 report; left untouched.

**Next pending phase:** none — 177 was the last; TODO.md is cleared.
This commit is contained in:
2026-10-11 15:33:39 -04:00
parent dd119d534a
commit 36eddce672
40 changed files with 2351 additions and 16 deletions
+49
View File
@@ -177,6 +177,55 @@ def effective_settings(
return effective
def favicon_svg(colors: dict[str, str]) -> str:
"""Render the browser-tab favicon artwork from the effective palette.
Phase 177 (task 01, A1 — owner-confirmed): the pure half of the
themed favicon. ``GET /favicon.svg`` (task 02) serves this, and
``frontend/assets/theme.js``'s ``themeFaviconDataUri`` (task 04)
is the client-side mirror — the two must NEVER diverge (the
unit pins here keep them aligned). The artwork is the SAME art as
the pre-phase static ``frontend/assets/favicon.svg`` (a hexagon +
center dot + 4 spokes, ``viewBox 0 0 64 64``); only the THREE
color roles swap, per the A1 mapping:
=========================== ===================
SVG attribute Palette role
=========================== ===================
hexagon path ``fill`` ``surface``
hexagon path ``stroke`` ``brand``
center circle ``fill`` ``brand``
spokes path ``stroke`` ``brand_ink``
=========================== ===================
**Byte-identical contract:** for the built-in palette (the three
role keys carrying their :data:`BUILTIN_COLORS` values) the output
is EXACTLY the static file's ``<svg>`` element — lowercase hex,
same attribute order (pinned by the golden constant in
``tests/unit/test_theming.py``; the static file's comment header
is file packaging, not art, and is NOT part of the contract).
The input is the full 20-value effective dict (mirroring
:func:`theme_style_tag`'s self-contained signature); the function
reads its three keys. Pure: dict in → str out (no DB, no app, no
defensive branches — the :func:`effective_settings` /
:data:`BUILTIN_COLORS` contract guarantees the values are
canonical lowercase 6-hex).
**No state colors:** the 8 semantic state colors are NOT in the
artwork — a favicon has no state meaning (the state families
encode *states* on the page; the tab icon is identity only).
"""
return (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">'
f'<path d="M32 4 55 18v28L32 60 9 46V18Z" fill="{colors["surface"]}"'
f' stroke="{colors["brand"]}" stroke-width="4" stroke-linejoin="round"/>'
f'<circle cx="32" cy="32" r="6.5" fill="{colors["brand"]}"/>'
f'<path d="M32 25.5V16M32 48v-9.5M25.5 32H16M48 32h-9.5"'
f' stroke="{colors["brand_ink"]}" stroke-width="3" stroke-linecap="round"/>'
"</svg>"
)
def theme_style_tag(colors: dict[str, str]) -> str:
"""The pre-paint inline theme tag (task 02's injection input).