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`
282 lines
12 KiB
Python
282 lines
12 KiB
Python
"""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)"
|
|
)
|