phase: 116_modal_scrollbar_theme
Build and Push Containers / build-and-push-app (push) Successful in 2m1s
Build and Push Containers / build-and-push-db (push) Successful in 13s

All verification complete — every gate re-run independently and green. Final report:

**Phase 116 final verification pass — all green (work was already in the tree; verified, nothing to fix)**

- **Verified** `styles.css`: `--scrollbar-thumb`/`--scrollbar-track` tokens in `:root` (color-mix derivations — follow the saved theme, not in BUILTIN_COLORS; contrast 3.73:1 ≥ 3:1, recomputed independently) + scoped rules `.doc-modal .doc-raw` (overflow-x already owned by the element)
- **Verified** E2E test 8 `test_raw_code_block_themed_scrollbar`: overflow, scroll action, computed-style theme assertions, screenshot
- **Screenshots visually confirmed**: headed `doc_modal_scrollbar_themed_1280.png` shows muted rounded thumb on dark track, no native light-gray bar; headless suite shot records scrolled state (headless Chromium paints no bar UI — documented in test docstring)

**Test / lint / coverage (re-run by me):**
- `uv run pytest tests/e2e/test_document_viewer.py -v --no-cov` → **8 passed** (isolation)
- `uv run pytest --cov=app --cov-report=term` → **2457 passed, app/ 99%** (>90% ✓)
- `uv run pytest tests/e2e/test_dark_tech_theme.py -v --no-cov` → **6 passed** (no token collision)
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings

**Completion criteria:**
1. Modal scrolls horizontally, scrollbar themed, screenshot shows no native bar — ✅
2. No global scrollbar change (all new selectors under `.doc-modal`; grep-confirmed), regressions green — ✅
3. pytest green, coverage >90%, e2e isolated green, ruff+pyright clean — ✅
4. `--no-gpg-sign` commit + phase move — harness job (left uncommitted in working tree, per rules)

**No defects found; no deviations. Next pending phase:** none — `todo/` contains only phase 116.
This commit is contained in:
2026-09-15 06:11:38 -04:00
parent 990c8adf13
commit adf31a4a35
16 changed files with 537 additions and 0 deletions
+141
View File
@@ -29,6 +29,10 @@ Test → story mapping (Playwright Mapping Rule):
inner content).
7. ``test_modal_theme_and_no_cdn`` — dark page background, the panel on
the Phase-08 surface colour, every asset same-origin or ``data:``.
8. ``test_raw_code_block_themed_scrollbar`` — phase 116: the raw code
block (``pre.doc-raw``) overflows horizontally, a scroll action moves
it, the themed scrollbar tokens are in effect via computed style, and
a screenshot records the themed (not native light-gray) bar.
"""
from __future__ import annotations
@@ -51,6 +55,7 @@ from e2e.auth_helpers import login
REPO = Path(__file__).resolve().parents[2]
FIXTURES = REPO / "tests" / "fixtures" / "docs"
SCREENSHOTS = REPO / ".agents" / "screenshots" # house convention for visual records
QUESTION = "How is my Kubernetes cluster set up?"
@@ -119,6 +124,33 @@ def _drill(page: Page, *names: str) -> None:
page.click(f'#folders-tbody a.folder-link:text-is("{name}")')
def _seed_long_line_doc() -> None:
"""Seed the synthetic phase-116 fixture (see the test docstring):
a non-markdown doc whose one long line is deliberately longer than
the modal width — the shape of the TODO's quest .pl ``quest::say``
line. Direct DB seed, the ``test_modal_xss_safe`` pattern (the
viewer is database-only; no chunks needed)."""
long_line = "quest::say(\"" + ("the quick brown fox jumps over the lazy dog " * 20) + "\")"
with SessionLocal() as db:
db.add(
Document(
source="docs",
path="notes/long-line.txt",
full_path="/tmp/long-line.txt",
title="Long Line Fixture",
content=(
"# long-line-fixture: the line below is deliberately\n"
"# longer than the document modal's width (phase 116).\n"
+ long_line
+ "\n"
),
content_hash="e" * 64,
indexed_at=datetime.now(UTC),
)
)
db.commit()
# ---------------------------------------------------------------------------
# 1. Chat source chip → SAME-PAGE modal (no new tab)
# ---------------------------------------------------------------------------
@@ -456,3 +488,112 @@ def test_modal_theme_and_no_cdn(
assert refs, "expected local asset references on the chat page"
for ref in refs:
assert ref.startswith(app_url) or ref.startswith("data:"), f"non-local: {ref}"
# ---------------------------------------------------------------------------
# 8. Phase 116: the raw code block scrolls horizontally with a themed
# scrollbar (task 01's scoped rules) — never the native light-gray bar
# ---------------------------------------------------------------------------
def test_raw_code_block_themed_scrollbar(
page: Page, app_url: str, mock_llm: int, db_ready: None
) -> None:
"""The modal's ``pre.doc-raw`` overflows horizontally, a horizontal
scroll action actually moves it, and task 01's themed scrollbar rules
are in effect (the screenshot is the visual record of the themed bar).
Document choice (the task says to check the fixture KB first): no
fixture doc qualifies — the longest RAW (non-markdown) fixture line is
91 chars (``homelab/networking/static-dns.json``) ≈ 742px at the
0.85rem mono font, well inside the ≈1058px pre client width at the
1280px fixture viewport (panel 1100px − content padding − pre border).
So this test seeds a synthetic doc instead (the
``test_modal_xss_safe`` direct-seed pattern): one ≈935-char
``quest::say(...)`` line — the shape of the TODO's observed quest
.pl line — which guarantees real horizontal overflow.
Screenshot note: headless Chromium paints NO scrollbars at all
(frame UI is omitted — verified on Chromium 151), so the suite's
screenshot (``doc_modal_scrollbar_1280.png``) is the record of the
scrolled modal state (long line mid-content = overflow + working
scroll, no native bar); the themed bar itself is asserted via
computed style here and recorded in the headed capture
``doc_modal_scrollbar_themed_1280.png`` (phase 116, task 02).
"""
_reset_db(mock_llm, seed=True)
_seed_long_line_doc()
login(page, app_url)
_drill(page, "docs", "notes")
row = page.locator("#docs-tbody tr", has_text="long-line.txt")
expect(row).to_have_count(1)
row.locator("td:nth-child(2) a.doc-link").click()
expect(page.locator(".doc-modal")).to_be_visible()
expect(page.locator("#doc-modal-title")).to_have_text("Long Line Fixture")
pre = page.locator("#doc-modal-content pre.doc-raw")
expect(pre).to_have_count(1)
# The overflow is real: the long line makes the content wider than the
# box (no line-wrap — locked A1, phase 116).
dims = pre.evaluate("el => ({sw: el.scrollWidth, cw: el.clientWidth})")
assert dims["sw"] > dims["cw"], f"no horizontal overflow: {dims}"
# The overflow is SCROLLED, not clipped: the element owns its
# overflow-x (task 01's comment in styles.css) and the themed rules
# are in effect — scrollbar-width: thin + scrollbar-color as a
# two-colour pair (the ::-webkit-scrollbar pseudos are not exposed via
# computed style; that half is the screenshot's job).
themed = page.evaluate(
"""() => {
const el = document.querySelector('#doc-modal-content pre.doc-raw');
const cs = getComputedStyle(el);
const probe = (name) => {
const s = document.createElement('span');
const hidden = 'position:absolute;visibility:hidden;';
s.style.cssText = hidden + 'background:var(' + name + ')';
document.body.appendChild(s);
const bg = getComputedStyle(s).backgroundColor;
s.remove();
return bg;
};
return {
overflowX: cs.overflowX,
width: cs.scrollbarWidth,
color: cs.scrollbarColor,
thumb: probe('--scrollbar-thumb'),
track: probe('--scrollbar-track'),
};
}"""
)
assert themed["overflowX"] == "auto", f"overflow-x is {themed['overflowX']}, not auto"
assert themed["width"] == "thin", f"scrollbar-width is {themed['width']}, not thin"
# The tokens are color-mix() derivations, so Chromium serializes the
# used values as color(srgb …) (plain rgb(…) or #hex also accepted).
colors = re.findall(
r"#[0-9a-fA-F]{3,8}\b|rgb\([^)]*\)|color\([^)]*\)", themed["color"]
)
assert len(colors) == 2, f"scrollbar-color {themed['color']!r} is not a two-colour pair"
assert colors[0] == themed["thumb"], (
f"scrollbar thumb {colors[0]} is not the theme's --scrollbar-thumb {themed['thumb']}"
)
assert colors[1] == themed["track"], (
f"scrollbar track {colors[1]} is not the theme's --scrollbar-track {themed['track']}"
)
# A horizontal scroll action actually moves it (no clipping — the
# rest of the line is reachable). The pre is freshly rendered, so
# scrollLeft starts at 0.
before = pre.evaluate("el => el.scrollLeft")
assert before == 0, f"expected scrollLeft 0 before the action, got {before}"
pre.evaluate("el => el.scrollBy({left: 300, behavior: 'instant'})")
after = pre.evaluate("el => el.scrollLeft")
assert after > 250, f"scrollBy(300) only moved to {after}"
# Visual record (house convention): the bar at the pre's bottom is the
# themed one — scroll to mid-content so the thumb sits mid-track.
pre.evaluate("el => el.scrollTo({left: el.scrollWidth / 2, behavior: 'instant'})")
shot = SCREENSHOTS / "doc_modal_scrollbar_1280.png"
page.screenshot(path=str(shot))
assert shot.exists() and shot.stat().st_size > 0