Files
brain-of-reese/tests/unit/test_table_wrap_stacking.py
T
ducoterra 0f6b9ff7e6 phase: 86_history_page_width
All criteria verified. Final report:

**Phase 86 final verification pass — all green, no defects found.**

- **Verified (previously implemented):** `position: relative` on shared `.table-wrap` (the one-rule A3 fix, +12 lines in `frontend/assets/styles.css`); unit pin `tests/unit/test_table_wrap_stacking.py` (3 tests); E2E story `tests/e2e/test_history_page_width.py` (5 tests: History 626→≤375, Tokens 618→≤375, SPA switch with window-sentinel, RAG regression, desktop 1280×800)
- **Tests/lint (this pass):** `uv run pytest` → 1720 passed · `uv run pytest --cov=app --cov-report=term-missing` → TOTAL **99%** (>90% floor) · E2E in isolation: `test_history_page_width.py` 5 passed, `test_chat_history.py` 5 passed, `test_api_tokens.py` 9 passed, `test_smoke.py` 3 passed · `uv run ruff check . && uv run pyright` → clean, 0 errors
- **Completion criteria:** ① 375px direct+SPA `scrollWidth ≤ innerWidth` on History/Tokens — PASS (E2E pins) ② in-card scroll preserved + full-width + a11y spans in DOM — PASS ③ Sources + desktop regression — PASS ④ full suite/coverage/E2E/lint gates — PASS ⑤ `git diff --stat` limited to `styles.css` (+12) + new test files + phase files, no `app/`/markup/JS — PASS ⑥ commit/move — left to harness per executor rules (working tree intact, `todo/` dir removal already reflected)
- **Notable:** red→green (CSS reverted → 626px failure) and manual live check already recorded in `.agents/reports/86_history_page_width/`; pre-existing untracked `.agents/remediation_plan.md` (Sep 7 security audit) untouched
- **Next pending phase:** `87_big_read_progress`
2026-09-08 02:38:45 -04:00

116 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Unit: the .table-wrap containing-block contract (phase 86, task 01).
TODO.md L4 (owner bug report, 2026-09-07): "The history page appears to
be the width of the table despite the table being scrollable. On mobile
this results in half the page being blank and awkwardly scrollable."
Bug basis (confirmed by live reproduction, 2026-09-07 — headless
Chromium, 375×812, signed-in admin): the History and Tokens tables
carry ``class="visually-hidden"`` on their ``<caption>`` and their
Actions column header — a ``position: absolute`` 1px clipped box. No
ancestor in the chain (th → table → .table-wrap → the view shell →
#main → body) was positioned, so the span's containing block was the
INITIAL containing block: its 1px box sat at the 640px table's right
edge and contributed to the DOCUMENT's scrollable overflow, bypassing
the card's own scroll clipping. Measured ``documentElement.scrollWidth``
at a 375px viewport: /history.html **626** (the page panned ~250px
into a blank region — the owner's "half the page being blank"),
/tokens.html **618** (the byte-identical defect, folded in per the
owner-confirmed A3 scope), /sources.html **375** (the RAG view is
clean — its table headers are visible text, no positioned hidden
spans). Hiding the span, OR making the card ``position: relative``,
both bring the scrollWidth back to 375.
The fix is ONE CSS property (owner decision A3): ``position: relative``
on the shared ``.table-wrap`` card — it becomes the containing block
for the ``.visually-hidden`` elements it hosts, so History AND Tokens
are fixed by the same rule with no markup change (the accessible
column name / caption stay) and no JS change. Zero-offset positioning
changes no layout: the spans stay clipped by their own
``clip: rect(0 0 0 0)`` + 1px box, and cards without such spans (the
RAG ``.docs-table``, the git-sources table) render unchanged.
This module pins the source-level contract (the house pattern of
``tests/unit/test_hamburger_nav.py``: read ``styles.css`` as text, no
browser) so the regression that omitted the containing block cannot
return.
"""
from __future__ import annotations
import re
from pathlib import Path
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
STYLES_CSS = FRONTEND / "assets" / "styles.css"
def _text(path: Path) -> str:
assert path.is_file(), f"missing frontend file: {path}"
return path.read_text(encoding="utf-8")
def _css() -> str:
"""styles.css with comments stripped (a comment may legally carry
braces — the brace-matching helper below must never see them)."""
return re.sub(r"/\*.*?\*/", "", _text(STYLES_CSS), flags=re.S)
def _rule_block(css: str, selector: str) -> str:
"""The first rule body for the EXACT selector ``selector`` (e.g.
``.table-wrap``). The lookbehind before the selector rejects
decorated variants: ``.table-wrap`` must NOT match ``.md-table-wrap`` (the
chat-answer markdown tables) nor ``.history-table-wrap`` /
``.tokens-table-wrap`` (the cards' extra classes), and an id rule
like ``#git-sources-table-wrap`` is never a match for the class
selector."""
m = re.search(r"(?<![\w-])" + re.escape(selector) + r"\s*\{([^}]*)\}", css)
assert m, f"missing rule for {selector!r}"
return m.group(1)
def test_table_wrap_is_a_positioned_containing_block() -> None:
"""THE regression pin: the shared .table-wrap card carries
position:relative — as its FIRST declaration — making it the
containing block for the position:absolute .visually-hidden
elements it hosts (the table caption + the Actions header span in
#view-history / #view-tokens). Without a positioned ancestor their
1px boxes are laid out against the initial containing block at the
640px table's right edge and leak into the document's scrollable
overflow (TODO.md L4 — the page appeared "the width of the
table": 626px on History, 618px on Tokens, at 375px)."""
block = _rule_block(_css(), ".table-wrap")
assert re.search(r"position:\s*relative", block), (
"the .table-wrap card must be position:relative (the "
"containing block for the .visually-hidden spans)"
)
assert block.lstrip().startswith("position: relative"), (
"position:relative must be the FIRST declaration of .table-wrap "
"(the containing-block contract precedes the card look)"
)
def test_table_wrap_keeps_the_horizontal_scroll_contract() -> None:
"""The fix preserves the phase-07 scroll contract: the card keeps
overflow-x:auto — at any viewport narrower than the tables' 640px
min-width the table scrolls INSIDE the card (AGENTS.md rule 5:
full-width tables stay) while the document itself no longer pans."""
block = _rule_block(_css(), ".table-wrap")
assert "overflow-x: auto" in block, (
"the .table-wrap card must keep overflow-x:auto (the in-card "
"scroll the fix protects)"
)
def test_the_fix_touched_only_the_shared_card() -> None:
"""The one-rule scope (A3): the fix lives on .table-wrap ONLY —
.md-table-wrap (chat answer tables — different class, different
context) and the #git-sources-table-wrap id card keep their
pre-phase-86 rules (no position declaration acquired)."""
css = _css()
for sel in (".md-table-wrap", "#git-sources-table-wrap"):
block = _rule_block(css, sel)
assert not re.search(r"position\s*:", block), (
f"{sel} must not acquire a position declaration (the fix is "
"the ONE .table-wrap rule)"
)