phase: 93_theme_semantic_completion
All verification is complete. Final report: **Phase 93 — Theme semantic completion: FINAL VERIFICATION PASS — ALL GREEN** - Verified full implementation in tree: migration `0016` (8 nullable semantic columns, applied at head), 17-var `BUILTIN_COLORS`/`COLOR_FIELDS`/`effective_settings`, API validation, `#view-theme` State-colors fieldset (17 pickers), `theme.js` FIELDS/PAIRS (5→8), `.page-head` surface panel (6 shell views + doc-edit + shared.html; login card / document sticky header audited as already-surfaced), mock_llm `content: None` fix - Fixed 2 pre-existing defects (both fail identically on baseline `d4f38ad`, proven via worktree A/B): `test_nav_rename_sources` — expected nav tail missing the phase-91 "Theme" link; `test_stale_ui_copy` — now truncates `saved_chats` before/after (house `test_suggestion_chips` pattern) so the seed-chip contract is deterministic on the shared dev DB (owner's 22 saved chats triggered phase-80 last-3-questions) - Tests: `uv run pytest --cov=app --cov-report=term-missing` → **1868 passed, app/ 99%** (>90% ✓); `uv run ruff check .` → clean; `uv run pyright` → **0 errors** - E2E: dedicated `uv run pytest tests/e2e/test_theme_semantic_completion.py -v --no-cov` → **8/8 in isolation** (all-gray 17-color theme: zero residual color on saved-result/Stale/Revoked/Local/tool-call elements, text labels intact, gray heads non-transparent, pre-paint tag, Reset → byte-identical no-tag); 15 theme/header/nav/responsive suites green in isolation; full 85-file combined run: only the 2 fixed pre-existing failures + 1 combined-run artifact (`test_sync_upload_progress`, green in isolation) - Completion criteria: (1) monochrome E2E ✓ (2) default byte-identical, no `#bor-theme` tag ✓ (3) all page heads on solid surface ✓ (4) suite/coverage/lint/E2E green ✓ (5) phases 01–92 no behavior change ✓ (6) commit left to harness per protocol - Notable: cleaned stray uvicorn leftovers from prior implementation pass (owner's `--reload` dev server untouched); no deviations from the phase design - Next pending phase: `94_ls_tree_drilldown`
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
"""Unit: the phase-93 page-head surface panel contract (task 03 —
|
||||
source pins).
|
||||
|
||||
Owner direction (TODO.md L3): "Also the header and description of each
|
||||
page needs a background - the grid makes it hard to read." Every
|
||||
page's ``h1`` + description (the shell's standard ``.page-head`` frame)
|
||||
sits on a solid ``var(--surface)`` panel — the house card language —
|
||||
so the 44px background grid never fights the heading text. Browser
|
||||
behavior (computed ``background-color`` non-transparent on every head,
|
||||
the History flex row + 360px wrap unbroken, the monochrome theme
|
||||
graying the panel) is E2E-gated by ``tests/e2e/
|
||||
test_theme_semantic_completion.py`` (task 04) and the existing
|
||||
header/nav/responsive suites; here we pin the source-level contract
|
||||
(house pattern: ``tests/unit/test_background_no_motion.py`` parses
|
||||
``styles.css`` rules):
|
||||
|
||||
* the shared ``.page-head`` rule declares the panel — a NON-transparent
|
||||
``background`` built from ``var(--surface)`` (no color literal of any
|
||||
kind, no blur — the phase-08 perf anchor), the 1px ``var(--line)``
|
||||
border, the house card radius, and card-rhythm padding;
|
||||
* the ``#view-history`` page-head keeps its SCOPED flex row (padding
|
||||
lives on the flex container — the base rule — never on the
|
||||
children), and the mobile ``.page-head-row { flex-wrap: wrap; }``
|
||||
survives;
|
||||
* every shell view that carries a ``.page-head`` in the shell markup
|
||||
is covered by the ONE rule (six views; ``#view-chat`` carries no
|
||||
``.page-head`` — its head is the navbar, audited), and
|
||||
``doc-edit.html`` keeps its ``.page-head``;
|
||||
* the standalone pages: the shared page's head (``h1#shared-title`` +
|
||||
the lede) is wrapped in the same ``.page-head`` class, while the two
|
||||
heads that already sit inside a surfaced card are deliberately
|
||||
UNTOUCHED (login's ``.login-card`` — surface card; document viewer's
|
||||
``.doc-titlebar`` inside the sticky surface ``.doc-header``).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
|
||||
STYLES_CSS = FRONTEND / "assets" / "styles.css"
|
||||
INDEX_HTML = FRONTEND / "index.html"
|
||||
SHARED_HTML = FRONTEND / "shared.html"
|
||||
DOC_EDIT_HTML = FRONTEND / "doc-edit.html"
|
||||
LOGIN_HTML = FRONTEND / "login.html"
|
||||
DOCUMENT_HTML = FRONTEND / "document.html"
|
||||
|
||||
# The six shell views whose h1 + description sit in a .page-head
|
||||
# (#view-chat carries none — its head is the navbar; audited).
|
||||
SHELL_VIEWS_WITH_PAGE_HEAD = (
|
||||
"view-tuning",
|
||||
"view-rag",
|
||||
"view-git-sources",
|
||||
"view-history",
|
||||
"view-tokens",
|
||||
"view-theme",
|
||||
)
|
||||
|
||||
|
||||
def _css() -> str:
|
||||
return STYLES_CSS.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _css_no_comments() -> str:
|
||||
return re.sub(r"/\*[\s\S]*?\*/", "", _css())
|
||||
|
||||
|
||||
def _find_rule(css: str, selector: str) -> re.Match[str] | None:
|
||||
"""The first top-level ``selector { ... }`` rule (comments
|
||||
stripped by the caller when prose must not interfere)."""
|
||||
return re.search(r"(?m)^" + re.escape(selector) + r"\s*\{([\s\S]*?)\n\}", css)
|
||||
|
||||
|
||||
def _rule_block(css: str, selector: str) -> str:
|
||||
rule = _find_rule(css, selector)
|
||||
assert rule, f"styles.css must define a {selector} rule"
|
||||
return rule.group(1)
|
||||
|
||||
|
||||
def _view_section(body: str, view_id: str) -> str:
|
||||
"""The view section slice — from its opening tag to the next
|
||||
sibling view (or ``</main>``). A first-``</section>`` slice would
|
||||
cut short: the views nest gate sections (``<section
|
||||
class="sources-gate">``) INSIDE, and the Git-sources / Theme
|
||||
``.page-head`` sits after the first nested close."""
|
||||
start = body.find(f'<section class="view" id="{view_id}"')
|
||||
assert start != -1, f"the #{view_id} section must be in the shell"
|
||||
boundaries = [
|
||||
e
|
||||
for e in (body.find('<section class="view"', start + 1), body.find("</main>", start))
|
||||
if e != -1
|
||||
]
|
||||
assert boundaries, "the shell must have a closing </main>"
|
||||
return body[start : min(boundaries)]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# The shared .page-head rule — the surface panel
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_page_head_rule_is_the_surface_panel() -> None:
|
||||
"""The one shared rule declares the panel: a non-transparent
|
||||
background from ``var(--surface)`` (itself tab-controlled — a
|
||||
monochrome theme grays the head automatically), the 1px
|
||||
``var(--line)`` border, the house card radius, and card-rhythm
|
||||
padding (the phase-93 panel: 1rem block / 1.25rem inline)."""
|
||||
block = _rule_block(_css_no_comments(), ".page-head")
|
||||
assert "background: var(--surface)" in block, (
|
||||
"the .page-head panel must fill with var(--surface) — solid, "
|
||||
"never transparent, never a literal"
|
||||
)
|
||||
assert "border: 1px solid var(--line)" in block, (
|
||||
"the panel must carry the house 1px --line border"
|
||||
)
|
||||
assert "border-radius: var(--radius)" in block, (
|
||||
"the panel must use the house card radius"
|
||||
)
|
||||
assert re.search(r"padding:\s*1rem\s+1\.25rem", block), (
|
||||
"the panel must carry the card-rhythm padding (1rem 1.25rem)"
|
||||
)
|
||||
|
||||
|
||||
def test_page_head_rule_has_no_color_literal_and_no_blur() -> None:
|
||||
"""Phase-92 invariant + phase-08 perf anchor: the panel rule
|
||||
introduces NO new color literal (hex / rgb / hsl / color-mix —
|
||||
``var(--…)`` only) and no filter/blur."""
|
||||
block = _rule_block(_css_no_comments(), ".page-head")
|
||||
assert not re.search(r"#[0-9a-fA-F]{3,8}\b", block), (
|
||||
"no hex color literal in the .page-head panel (phase-92 invariant)"
|
||||
)
|
||||
for func in ("rgb(", "hsl(", "color-mix("):
|
||||
assert func not in block, f"no {func}… literal in the .page-head panel"
|
||||
assert "filter" not in block and "blur" not in block, (
|
||||
"no filter/blur in the .page-head panel (phase-08 perf anchor)"
|
||||
)
|
||||
|
||||
|
||||
def test_page_head_background_is_solid_not_translucent() -> None:
|
||||
"""The panel is SOLID — the assumption the phase locked (not
|
||||
translucent, not a full-bleed band, no blur): the background
|
||||
declaration names exactly the surface variable, nothing mixed."""
|
||||
block = _rule_block(_css_no_comments(), ".page-head")
|
||||
backgrounds = re.findall(r"(?m)^\s*background(?:-color)?:\s*([^;]+);", block)
|
||||
assert backgrounds == ["var(--surface)"], (
|
||||
f"the .page-head background must be exactly var(--surface), "
|
||||
f"got {backgrounds}"
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Layout safety — the History flex row and the mobile wrap
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_history_page_head_keeps_its_scoped_flex_row() -> None:
|
||||
"""The ``#view-history .page-head`` flex row (title left, refresh
|
||||
pill right — phase 77) survives the panel: the scoped rule keeps
|
||||
its flex declarations and adds NO padding of its own (the task-03
|
||||
rule — padding on the flex container, i.e. the base ``.page-head``
|
||||
rule, never on the children, so space-between + align stay intact
|
||||
inside the padded box)."""
|
||||
scoped = _rule_block(_css_no_comments(), "#view-history .page-head")
|
||||
for decl in (
|
||||
"display: flex",
|
||||
"flex-wrap: wrap",
|
||||
"align-items: flex-start",
|
||||
"justify-content: space-between",
|
||||
):
|
||||
assert decl in scoped, f"#view-history .page-head must keep {decl!r}"
|
||||
assert not re.search(r"(?m)^\s*padding\b", scoped), (
|
||||
"the scoped History rule must not add its own padding — the "
|
||||
"base .page-head rule (the flex container) carries it"
|
||||
)
|
||||
|
||||
|
||||
def test_mobile_page_head_row_wrap_survives() -> None:
|
||||
"""≤640px: ``.page-head-row { flex-wrap: wrap; }`` (the RAG head's
|
||||
Sync pill drops below the title at full width) still exists in the
|
||||
mobile block — the wrap keeps working INSIDE the panel."""
|
||||
mobile = re.search(r"@media \(max-width: 640px\) \{([\s\S]*?)\n\}\n", _css())
|
||||
assert mobile, "the mobile media query must exist"
|
||||
assert ".page-head-row { flex-wrap: wrap; }" in mobile.group(1), (
|
||||
"the mobile .page-head-row wrap must survive the panel"
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Coverage — every head that sits on the grid gets the panel
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_every_shell_view_with_a_page_head_is_covered() -> None:
|
||||
"""The ONE rule covers every shell view that carries the class:
|
||||
all six do (their ``class="page-head"`` div is static markup in
|
||||
the shell); #view-chat carries NO .page-head (its head is the
|
||||
navbar — audited, untouched)."""
|
||||
body = INDEX_HTML.read_text(encoding="utf-8")
|
||||
for view_id in SHELL_VIEWS_WITH_PAGE_HEAD:
|
||||
assert 'class="page-head"' in _view_section(body, view_id), (
|
||||
f"#{view_id} must keep its .page-head (the shared rule panels it)"
|
||||
)
|
||||
assert 'class="page-head"' not in _view_section(body, "view-chat"), (
|
||||
"#view-chat carries no .page-head (its head is the navbar)"
|
||||
)
|
||||
|
||||
|
||||
def test_doc_edit_page_head_is_covered() -> None:
|
||||
"""doc-edit.html's head already IS a .page-head (h1 'Edit doc' +
|
||||
the .page-sub lede) — the shared rule panels it with no markup
|
||||
change."""
|
||||
html = DOC_EDIT_HTML.read_text(encoding="utf-8")
|
||||
head = re.search(
|
||||
r'<div class="page-head">\s*<h1>Edit doc</h1>[\s\S]*?class="page-sub"',
|
||||
html,
|
||||
)
|
||||
assert head, "doc-edit.html must keep its .page-head (h1 + page-sub)"
|
||||
|
||||
|
||||
def test_shared_page_head_is_wrapped_in_page_head() -> None:
|
||||
"""The shared page's head (``h1#shared-title`` + the read-only
|
||||
lede) was a direct child of the grid-exposed ``.shared-shell`` —
|
||||
it is now wrapped in the same ``.page-head`` class, so the shared
|
||||
rule panels it (the h1 keeps the ``.page-head h1`` size per the
|
||||
``#shared-title`` rule)."""
|
||||
html = SHARED_HTML.read_text(encoding="utf-8")
|
||||
wrapped = re.search(
|
||||
r'<div class="page-head">\s*'
|
||||
r"<h1 id=\"shared-title\">Shared conversation</h1>\s*"
|
||||
r'<p class="shared-note">Shared via Brain of Reese — read-only\.</p>\s*'
|
||||
r"</div>",
|
||||
html,
|
||||
)
|
||||
assert wrapped, (
|
||||
"shared.html must wrap the h1 + lede in a .page-head div"
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Deliberate skips — heads already inside a surfaced card
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_login_head_is_already_carded_and_untouched() -> None:
|
||||
"""login.html: h1#login-title lives inside section.login-card,
|
||||
which is ALREADY the surface card (var(--surface) fill + border +
|
||||
radius + shadow) — the TODO targets grid-exposed text, so the
|
||||
login head gets no double panel."""
|
||||
html = LOGIN_HTML.read_text(encoding="utf-8")
|
||||
card = re.search(
|
||||
r'<section class="login-card"[^>]*>[\s\S]*?'
|
||||
r'<h1 id="login-title">Sign in</h1>',
|
||||
html,
|
||||
)
|
||||
assert card, "the login h1 must sit inside the .login-card"
|
||||
card_css = _rule_block(_css_no_comments(), ".login-card")
|
||||
assert "background: var(--surface)" in card_css
|
||||
assert "border: 1px solid var(--line)" in card_css
|
||||
assert "border-radius: var(--radius)" in card_css
|
||||
assert 'class="page-head"' not in html, (
|
||||
"login.html must not gain a .page-head (the card already panels it)"
|
||||
)
|
||||
|
||||
|
||||
def test_document_head_is_already_carded_and_untouched() -> None:
|
||||
"""document.html: h1#doc-title lives in the .doc-titlebar row 2
|
||||
inside the STICKY .doc-header, which is already surface-filled
|
||||
(var(--surface) — the two-row pinned bar) — no double panel."""
|
||||
html = DOCUMENT_HTML.read_text(encoding="utf-8")
|
||||
titlebar = re.search(
|
||||
r'<div class="doc-titlebar">[\s\S]*?<h1 id="doc-title">', html
|
||||
)
|
||||
assert titlebar, "the document h1 must sit inside the .doc-titlebar"
|
||||
header_css = _rule_block(_css_no_comments(), ".doc-header")
|
||||
assert "background: var(--surface)" in header_css, (
|
||||
"the sticky .doc-header must stay surface-filled"
|
||||
)
|
||||
assert 'class="page-head"' not in html, (
|
||||
"document.html must not gain a .page-head (the titlebar is "
|
||||
"already inside the surface header)"
|
||||
)
|
||||
Reference in New Issue
Block a user