Patch notes — design system, review, cleanup & search fix (2026-06-12)
Branch: dev (not yet merged to main, so not deployed — main is the prod trigger).
This session built one coherent design system across the whole property, reviewed and
hardened it, did a safe maintainability pass on the admin server, hardened the smoke
harness, and fixed a real search-snippet bug.
1 — Total coherent design system ✅
Problem: the site looked branded on the front page but drifted everywhere else —
three styling surfaces (landing inline <style>, editorial.css, the admin
ADMIN_CSS) each carried their own variable vocabulary plus a dead warm-cream palette,
all only coincidentally resolving to a similar navy. .card meant three different
things; the theme toggle key was split (skui-theme vs ethica-theme).
Fix: one token spine + a shared component library, consumed identically by the Hugo pages and the Python admin (both link the same stable, un-fingerprinted static files):
static/css/theme.css— single source of truth: brand/logo/font/color/radius/shadow/ layout/space tokens. Dark:rootdefault +:root[data-theme="light"].static/css/components.css— shared primitives on tokens:.btn,.card/.panel(+--feature/--project),.badge,.chip,.field,.section,.status-*,.reveal, and the site chrome.ev-top/.ev-nav/.ev-foot.static/css/admin.css— admin-only layout/density on the same tokens.assets/css/editorial.cssslimmed to layout-only (642 → ~300 lines); every legacyvar(--ink/--paper/--green/…)rewritten to canonical--color-*; “Playfair Display” →--font-display(Comfortaa).- Theme key unified to
ethica-themeeverywhere (pre-paint reader + menu writer). - Documented in
assets/css/DESIGN.md(token catalog, load order, “add a module without drift” guide).
2 — Unified chrome, background, and Products rename ✅
- Header/footer unified across every page including home (dropped the per-page
{{ if not .IsHome }}guards;index.htmllost its inline header/footer and now inherits the shared partials viabaseof.html) — fixes the “logo not in the same place” / “menu differs per page” reports. - Front-page backdrop everywhere: the radial+linear glow moved into a
--bg-apptoken referenced by botheditorial.cssandadmin.css. - Blog list/single, products, taxonomy/term redesigned to the glass-navy card
language; single posts drop the rail. Dead
editorial-card.htmlremoved. - Admin “Projects” → “Products” (page H1 + side-menu label); the
/admin/projectsroute and theprojectsDB table are unchanged. - Header alignment:
html { scrollbar-gutter: stable; }so the centered header/footer no longer shift sideways between short (no-scrollbar) and tall pages.
3 — Code review of the design system + 12 fixes ✅
Extra-high-effort recall review (review/2026-06-12-design-system.md). No crash-class
bugs; 12 findings, all fixed:
- Regressions: added
.badge--maintained(a documented status value had no modifier → silent grey pill);card→panelon the two add-row admin forms (they hover-lifted); restored.chip .icaccent (deleted in the slim-down, markup still emitted it). - Cleanup: removed dead
.abar/.apublic/.aright; unifiedhtml[data-theme]→:root[data-theme="light"]; extracted--bg-app(de-duplicated the gradient); font literal →var(--font-sans); admin icon legend reuses.chip; removedPUBLIC_NAVand the no-op.ttclass.
4 — Stale-cache background follow-up documented ✅ (known issue, open)
Returning browsers briefly showed no page background. Not a code defect: the shared
CSS is served at stable un-fingerprinted URLs, so a browser with a cached old theme.css
(no --bg-app) paired with the fresh fingerprinted editorial.css (which needs it) drops
the background. Workaround: hard refresh. Proper fix (open): cache-bust the shared
/css/* links in ed-head.html + server.py:chrome_head(). Recorded in the review note.
5 — Safe maintainability pass (admin server) ✅
Behavior-preserving; no routes/APIs/schema/auth/env-var changes (review/2026-06-12- maintainability-pass.md):
csrf_input(token)helper replaces 14 copies of the hidden CSRF-field literal.- The password-change handler now uses the existing
ITERATIONSconstant instead of two hardcoded210000literals (identical value). - Removed vestigial
DB_PATH(adata/admin.sqlitepath from before the Postgres migration) and itsmkdir; startup banner now readsdb=postgres. - Comments on the non-obvious blocks (auth constants, in-memory login throttle, the
LIST_SPECSconfig-driven-CRUD contract).
6 — Smoke harness hardened ✅
The run-ethica-no smoke asserted admin login → 302 but never rendered an authed page,
so a self-recursing form helper that 500’d every admin editor passed an 8-check smoke.
Added a 9th check (admin form renders (authed)): log in keeping the session cookie, GET
/admin/status, assert a rendered name=csrf value= field. Now 9/9. Documented + a
battle-scar gotcha in the skill.
7 — Search snippet corruption fixed ✅
Problem: /api/search snippets leaked literal ,StopSel= and a default </b> around
the matched term.
Fix: Postgres ts_headline mis-parses unquoted empty options —
StartSel=,StopSel=,… made StartSel the literal ,StopSel= and left StopSel at its
</b> default. Quoting the empties (StartSel="",StopSel="") yields the intended
marker-free plain-text excerpt. Verified against Postgres directly and on live
/api/search.
Files touched
| Area | Files |
|---|---|
| Tokens / components | static/css/theme.css, static/css/components.css, static/css/admin.css |
| Page CSS / layout | assets/css/editorial.css, assets/css/DESIGN.md |
| Templates | layouts/index.html, layouts/_default/{single,list,taxonomy,term}.html, layouts/partials/{ed-masthead,ed-footer,ed-head,ed-row}.html, layouts/products/list.html (deleted editorial-card.html) |
| Admin server | server.py, scripts/init_db.py |
| JS | assets/js/sidebar-hydrate.js |
| Harness | .claude/skills/run-ethica-no/{SKILL.md,smoke.sh} |
| Docs / review | review/2026-06-12-design-system.md, review/2026-06-12-maintainability-pass.md |
Follow-ups / notes
- Cache-bust the shared
/css/*links (section 4) — the one open item; otherwise returning browsers can need a hard refresh after a token change. UPLOAD_DIRis defined andmkdir’d but never read/written — a stub; wire or drop.server.pyis a ~1200-line monolith; a module split (auth/render/admin) is deferred (import-coupling risk; the single-file stdlib server is well-commented).devis ahead ofmainand not deployed — merge tomainwhen ready to ship.