Single consolidated commit for four completed, validated phases (77, 78, 79, 80). The pipeline run left all work uncommitted because the harness commits only with PHASE_COMMIT=1 while child executors are forbidden from committing; the phases themselves all passed validation and moved to .agents/phases/complete/. Phase 77 — navbar view refresh - router.js dispatches bor:view-refresh on re-show / active re-click / popstate (gated on wasMounted; first show and boot exempt) - History / RAG / Sources / Tuning re-fetch on refresh (admin branch); Chat deliberately excluded (stream survival) - History "Refresh" button (admin-only, in-flight disable + status line) - New story suite tests/e2e/test_navbar_refresh.py (7 tests) Phase 78 — static background - Removed the animated glow layers; static 44px grid over the flat --bg canvas; default and reduced-motion renders byte-identical - Updated background/theme E2E suites; removed bg-glow test pins Phase 79 — API tokens - api_tokens model + migration 0012; hash-only token service - Admin tokens API + Tokens admin view; POST /api/token-auth; live-revoking require_user on chat / suggestions / document content - Frontend token gate with localStorage cache; anonymous E2E suites migrated to token login - New story suite tests/e2e/test_api_tokens.py (9 tests) Phase 80 — history suggestion chips - last_questions() endpoint with SEED fallback; startNewChat() refetch - Seed-semantics docs (config.py, .env.example, README) - Integration state matrix + E2E suite rewritten to the 4 chip states Also included: phase-76 report artifacts and the repo restore-test-db skill (previously untracked), scripts/* ruff fixes from phase 77. Final gate state (phase 80 final pass, covers everything above): - uv run pytest --cov=app → 1637 passed, 0 failed, app/ coverage 99% - uv run ruff check . && uv run pyright → clean, 0 errors - Per-phase story E2E suites green in isolation
191 lines
8.0 KiB
Python
191 lines
8.0 KiB
Python
"""Unit: the phase-78 static-background contract (source pins).
|
|
|
|
Owner direction (TODO.md L4, recorded per AGENTS.md rule 3): "Remove the
|
|
animated css background, it's too resource intensive" — the three
|
|
opacity-fading glow spots (``body::after`` / ``html::before`` /
|
|
``html::after``), their glow keyframes, and the
|
|
``prefers-reduced-motion`` rule whose only job was stilling those layers
|
|
are deleted from ``styles.css``. The 44px grid texture on
|
|
``body::before`` STAYS — it is static (zero animation cost).
|
|
|
|
Supersedes the phase-25 fading-glow contract (superseded chain
|
|
08 → 25 → 78); the phase-25 unit source-pin suite
|
|
(``tests/unit/test_background_animation.py``) is deleted with its
|
|
premise (three fading glows on named keyframe cycles).
|
|
|
|
Story: n/a (TODO-derived). Browser behavior (no background animation in
|
|
a real viewport, the grid still painted, no occlusion, no overflow) is
|
|
E2E-covered by ``tests/e2e/test_background_no_motion.py`` (task 02).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
STYLES_CSS = (
|
|
Path(__file__).resolve().parents[2] / "frontend" / "assets" / "styles.css"
|
|
)
|
|
|
|
GRID_LAYER = "body::before" # the static 44px grid — STAYS
|
|
# The phase-25 glow layers — all three deleted in phase 78 (they were
|
|
# the ONLY animated part of the background).
|
|
GLOW_LAYERS = ("body::after", "html::before", "html::after")
|
|
|
|
|
|
def _css() -> str:
|
|
return STYLES_CSS.read_text(encoding="utf-8")
|
|
|
|
|
|
def _css_no_comments() -> str:
|
|
"""styles.css with /* … */ comments stripped — for functional rule
|
|
checks that must not trip on explanatory prose."""
|
|
return re.sub(r"/\*[\s\S]*?\*/", "", _css())
|
|
|
|
|
|
def _find_rule(css: str, selector: str) -> re.Match[str] | None:
|
|
"""The first top-level ``selector { ... }`` rule, or None (the
|
|
deleted glow layers must be ABSENT, so this may not assert)."""
|
|
return re.search(r"(?m)^" + re.escape(selector) + r"\s*\{([\s\S]*?)\n\}", css)
|
|
|
|
|
|
def _rule_block(css: str, selector: str) -> str:
|
|
"""Body of the first ``selector { ... }`` rule (top-level, no
|
|
nesting)."""
|
|
rule = _find_rule(css, selector)
|
|
assert rule, f"styles.css must define a {selector} rule"
|
|
return rule.group(1)
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# The animated part is gone — no glow layers, no glow keyframes
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
def test_no_bg_glow_keyframes_remain() -> None:
|
|
"""The three glow @keyframes blocks are deleted — the names must not
|
|
appear anywhere in the file (no declaration, no keyframe block, no
|
|
stale comment). Pinned by the ``bg-`` prefix: no token carrying the
|
|
background keyframe namespace may survive (the prefix is a plain
|
|
literal, so this pin itself carries none of the deleted names)."""
|
|
assert "bg-" not in _css(), (
|
|
"no token in the background keyframe namespace (bg-*) may remain "
|
|
"in styles.css — the glow keyframes and their declarations are deleted"
|
|
)
|
|
assert re.search(r"@keyframes bg-", _css_no_comments()) is None, (
|
|
"no background @keyframes may remain in styles.css"
|
|
)
|
|
|
|
|
|
def test_glow_layers_are_deleted() -> None:
|
|
"""body::after / html::before / html::after no longer exist as CSS
|
|
rules — the layers (their background-images, their animations, their
|
|
fixed/z-index:-1 boxes) are gone from the page entirely."""
|
|
rules = _css_no_comments()
|
|
for sel in GLOW_LAYERS:
|
|
assert _find_rule(rules, sel) is None, (
|
|
f"{sel} must be deleted (the phase-78 static contract)"
|
|
)
|
|
|
|
|
|
def test_no_background_layer_declares_animation() -> None:
|
|
"""None of the four background pseudo-element selectors carries an
|
|
``animation:`` declaration — the three glow selectors are ABSENT,
|
|
and the surviving grid layer is animation-free."""
|
|
rules = _css_no_comments()
|
|
for sel in (GRID_LAYER, *GLOW_LAYERS):
|
|
rule = _find_rule(rules, sel)
|
|
if rule is None:
|
|
continue # deleted layer — nothing to animate
|
|
assert "animation" not in rule.group(0), (
|
|
f"{sel} must not animate (static background contract)"
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# The static grid STAYS — byte-identical texture, no animation
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
def test_grid_layer_is_static_and_unchanged() -> None:
|
|
"""The owner removed the animated part, not the grid: body::before
|
|
keeps 44px cells, 1px lines at the fixed 60% line alpha (warm
|
|
rebrand tone), and the widened radial mask (both the -webkit- and
|
|
standard mask properties) — and carries NO animation."""
|
|
block = _rule_block(_css(), GRID_LAYER)
|
|
assert "background-size: 44px 44px" in block
|
|
assert (
|
|
"linear-gradient(to right, rgb(74 38 38 / 0.6) 1px, transparent 1px)" in block
|
|
), "grid must keep horizontal 1px lines at 60% line alpha"
|
|
assert (
|
|
"linear-gradient(to bottom, rgb(74 38 38 / 0.6) 1px, transparent 1px)" in block
|
|
), "grid must keep vertical 1px lines at 60% line alpha"
|
|
mask = "radial-gradient(140% 110% at 50% 0%, black 40%, transparent 90%)"
|
|
assert f"-webkit-mask-image: {mask};" in block
|
|
assert f"mask-image: {mask};" in block
|
|
assert "animation" not in block, "the grid must stay static (no animation)"
|
|
|
|
|
|
def test_grid_layer_plumbing() -> None:
|
|
"""The surviving grid layer stays behind the content and can never
|
|
intercept input: fixed, full-viewport, z-index -1, pointer-events
|
|
none, with pseudo content (UI Structure Check: the fixed; inset: 0
|
|
layer adds no width — no 360px overflow)."""
|
|
block = _rule_block(_css(), GRID_LAYER)
|
|
assert "position: fixed" in block
|
|
assert "inset: 0" in block
|
|
assert "z-index: -1" in block
|
|
assert "pointer-events: none" in block
|
|
assert 'content: ""' in block
|
|
|
|
|
|
def test_html_owns_canvas_and_body_stays_transparent() -> None:
|
|
"""The no-occlusion contract survives the deletion: <html> keeps the
|
|
var(--bg) canvas; <body> stays transparent and non-stacking — or the
|
|
z-index:-1 grid layer would be painted over."""
|
|
html_block = _rule_block(_css(), "html")
|
|
assert "background: var(--bg)" in html_block, (
|
|
"html must keep background: var(--bg) (the page canvas)"
|
|
)
|
|
body_block = _rule_block(_css(), "body")
|
|
assert "background: transparent" in body_block, (
|
|
"body must keep background: transparent so the grid shows"
|
|
)
|
|
for prop in ("z-index", "transform", "opacity", "filter"):
|
|
assert prop + ":" not in body_block, (
|
|
f"body must not create a stacking context (found {prop})"
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Reduced motion + phase-08 anchors
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
def test_background_reduced_motion_block_is_deleted() -> None:
|
|
"""The prefers-reduced-motion rule whose only job was stilling the
|
|
background layers (body::before, body::after, html::before,
|
|
html::after → animation: none) is deleted WITH the layers. The
|
|
unrelated reduced-motion blocks (typing dots, spinner, toasts, nav
|
|
slide, …) stay untouched."""
|
|
blocks = re.findall(
|
|
r"@media \(prefers-reduced-motion: reduce\)\s*\{([\s\S]*?)\n\}", _css()
|
|
)
|
|
assert blocks, "the unrelated reduced-motion blocks must survive"
|
|
for i, block in enumerate(blocks):
|
|
for sel in (GRID_LAYER, *GLOW_LAYERS):
|
|
assert sel not in block, (
|
|
f"reduced-motion block {i} still references background layer {sel}"
|
|
)
|
|
|
|
|
|
def test_no_filter_no_blur() -> None:
|
|
"""The phase-08 performance anchor survives the deletion: no
|
|
``filter`` in the grid rule and no ``blur`` anywhere in styles.css
|
|
(comments stripped)."""
|
|
assert "filter" not in _rule_block(_css(), GRID_LAYER), (
|
|
"the grid layer must not use any filter"
|
|
)
|
|
assert "blur" not in _css_no_comments(), (
|
|
"no filter: blur anywhere in styles.css (phase-08 perf anchor)"
|
|
)
|