phase: 177_themed_favicon
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:
@@ -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).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user