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
@@ -0,0 +1,21 @@
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.
@@ -0,0 +1,103 @@
........................................................................ [ 2%]
........................................................................ [ 5%]
........................................................................ [ 8%]
........................................................................ [ 11%]
........................................................................ [ 14%]
........................................................................ [ 17%]
........................................................................ [ 20%]
........................................................................ [ 23%]
........................................................................ [ 26%]
........................................................................ [ 29%]
........................................................................ [ 32%]
........................................................................ [ 35%]
........................................................................ [ 38%]
........................................................................ [ 41%]
........................................................................ [ 43%]
........................................................................ [ 46%]
........................................................................ [ 49%]
........................................................................ [ 52%]
........................................................................ [ 55%]
........................................................................ [ 58%]
........................................................................ [ 61%]
........................................................................ [ 64%]
........................................................................ [ 67%]
........................................................................ [ 70%]
........................................................................ [ 73%]
........................................................................ [ 76%]
........................................................................ [ 79%]
........................................................................ [ 82%]
........................................................................ [ 84%]
........................................................................ [ 87%]
........................................................................ [ 90%]
........................................................................ [ 93%]
........................................................................ [ 96%]
........................................................................ [ 99%]
......... [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain-of-reese/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.14.7-final-0 ________________
Name Stmts Miss Cover
--------------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 52 0 100%
app/api/chat.py 224 1 99%
app/api/chats.py 110 0 100%
app/api/config.py 13 0 100%
app/api/doc_drafts.py 99 0 100%
app/api/docs.py 156 1 99%
app/api/git_sources.py 232 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 33 0 100%
app/api/sync.py 139 0 100%
app/api/tokens.py 40 0 100%
app/api/ui_settings.py 55 0 100%
app/config.py 210 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 124 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/errors.py 5 0 100%
app/core/logging.py 13 0 100%
app/core/rate_limit.py 44 0 100%
app/core/security_headers.py 20 0 100%
app/core/theming.py 38 0 100%
app/core/tokens.py 44 0 100%
app/db.py 22 0 100%
app/main.py 66 0 100%
app/models.py 128 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 317 1 99%
app/rag/archive_upload.py 134 0 100%
app/rag/chunker.py 206 4 98%
app/rag/doc_dates.py 18 0 100%
app/rag/folder_summaries.py 123 0 100%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 215 3 99%
app/rag/llm.py 244 1 99%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 192 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 329 0 100%
--------------------------------------------------
TOTAL 4147 16 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414).
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
validation OK
@@ -0,0 +1,18 @@
Task 01 complete.
**Implemented**
- `frontend/assets/styles.css` — added `--scrollbar-thumb` / `--scrollbar-track` tokens to the `:root` block, derived via `color-mix()` from identity tokens (same pattern as phase-92 derived state colors: not stored, not tab-controlled, not in `BUILTIN_COLORS` — every saved theme repaints them)
- Added the scoped rules on `.doc-modal .doc-raw` (the `pre` that owns its `overflow-x: auto` + `white-space: pre`): `scrollbar-width: thin` + `scrollbar-color` (Firefox) and the `::-webkit-scrollbar{height:8px}` / `-track` / `-thumb{bg, radius:4px}` pseudos (Chromium) — no global restyle
**Results**
- `uv run pytest --cov=app --cov-report=term` → 2457 passed, 99% coverage on `app/` (gate >90%)
- `uv run pytest tests/e2e/test_dark_tech_theme.py -v --no-cov` → 6 passed (in isolation)
- `uv run pytest tests/e2e/test_document_viewer.py -v --no-cov` → 7 passed (regression)
- `uv run ruff check . && uv run pyright` → All checks passed / 0 errors
- Headless-Chromium sanity check: computed `scrollbar-color` = thumb `#796b6b` on track `#170e0e` (3.7:1 ≥ 3:1 non-text AA), 8px webkit bar, 4px radius, pre scrolls (1598 > 1056)
**Decisions**
- Track = `color-mix(surface 75%, bg)` (near the `.doc-raw` bg, slightly recessed); thumb = `color-mix(ink-soft 60%, surface)` for a muted ≥3:1 thumb (50% measured 2.97:1 — rejected)
- No `app/` code change; no line-wrap (locked A1 honored)
**Next pending task:** `02_scrollbar_e2e_check.md` (E2E scroll check + screenshot)
@@ -0,0 +1,103 @@
........................................................................ [ 2%]
........................................................................ [ 5%]
........................................................................ [ 8%]
........................................................................ [ 11%]
........................................................................ [ 14%]
........................................................................ [ 17%]
........................................................................ [ 20%]
........................................................................ [ 23%]
........................................................................ [ 26%]
........................................................................ [ 29%]
........................................................................ [ 32%]
........................................................................ [ 35%]
........................................................................ [ 38%]
........................................................................ [ 41%]
........................................................................ [ 43%]
........................................................................ [ 46%]
........................................................................ [ 49%]
........................................................................ [ 52%]
........................................................................ [ 55%]
........................................................................ [ 58%]
........................................................................ [ 61%]
........................................................................ [ 64%]
........................................................................ [ 67%]
........................................................................ [ 70%]
........................................................................ [ 73%]
........................................................................ [ 76%]
........................................................................ [ 79%]
........................................................................ [ 82%]
........................................................................ [ 84%]
........................................................................ [ 87%]
........................................................................ [ 90%]
........................................................................ [ 93%]
........................................................................ [ 96%]
........................................................................ [ 99%]
......... [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain-of-reese/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.14.7-final-0 ________________
Name Stmts Miss Cover
--------------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 52 0 100%
app/api/chat.py 224 1 99%
app/api/chats.py 110 0 100%
app/api/config.py 13 0 100%
app/api/doc_drafts.py 99 0 100%
app/api/docs.py 156 1 99%
app/api/git_sources.py 232 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 33 0 100%
app/api/sync.py 139 0 100%
app/api/tokens.py 40 0 100%
app/api/ui_settings.py 55 0 100%
app/config.py 210 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 124 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/errors.py 5 0 100%
app/core/logging.py 13 0 100%
app/core/rate_limit.py 44 0 100%
app/core/security_headers.py 20 0 100%
app/core/theming.py 38 0 100%
app/core/tokens.py 44 0 100%
app/db.py 22 0 100%
app/main.py 66 0 100%
app/models.py 128 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 317 1 99%
app/rag/archive_upload.py 134 0 100%
app/rag/chunker.py 206 4 98%
app/rag/doc_dates.py 18 0 100%
app/rag/folder_summaries.py 123 0 100%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 215 3 99%
app/rag/llm.py 244 1 99%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 192 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 329 0 100%
--------------------------------------------------
TOTAL 4147 16 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414).
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
validation OK
@@ -0,0 +1,15 @@
Task 02 complete — all gates green.
**Implemented**
- `tests/e2e/test_document_viewer.py` — new test 8 `test_raw_code_block_themed_scrollbar`: seeds a synthetic `notes/long-line.txt` with a ~935-char `quest::say(…)` line (fixture KB checked first — longest raw line is 91 chars ≈ 742px < ~1058px pre client width, so no fixture doc overflows; choice noted in docstring), then asserts real overflow (`scrollWidth > clientWidth`), `overflow-x: auto` + `scrollbar-width: thin`, computed `scrollbar-color` = the `--scrollbar-thumb`/`--scrollbar-track` theme pair, `scrollBy(300)` moves `scrollLeft`, and writes a screenshot to `.agents/screenshots/`.
**Results**
- `uv run pytest tests/e2e/test_document_viewer.py -v --no-cov` → 8 passed (in isolation)
- `uv run pytest tests/e2e/test_dark_tech_theme.py -v --no-cov` → 6 passed
- `uv run pytest --cov=app --cov-report=term` → 2457 passed, 99% coverage (gate >90%)
- `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings
**Notable decisions**
- Headless Chromium (151) paints no scrollbars at all (verified: no pixels in the bar strip), so the suite's screenshot (`doc_modal_scrollbar_1280.png`) records the scrolled modal state, while the themed bar itself is asserted via computed style and recorded in a one-off **headed** capture `doc_modal_scrollbar_themed_1280.png` (visually verified: muted rounded thumb mid-track on the dark recessed track, no native light-gray bar). The temp headed-capture test was deleted after use.
**Next pending task:** none in this phase — both task files of `116_modal_scrollbar_theme` are done; the phase is ready for the harness commit/move.
@@ -0,0 +1,103 @@
........................................................................ [ 2%]
........................................................................ [ 5%]
........................................................................ [ 8%]
........................................................................ [ 11%]
........................................................................ [ 14%]
........................................................................ [ 17%]
........................................................................ [ 20%]
........................................................................ [ 23%]
........................................................................ [ 26%]
........................................................................ [ 29%]
........................................................................ [ 32%]
........................................................................ [ 35%]
........................................................................ [ 38%]
........................................................................ [ 41%]
........................................................................ [ 43%]
........................................................................ [ 46%]
........................................................................ [ 49%]
........................................................................ [ 52%]
........................................................................ [ 55%]
........................................................................ [ 58%]
........................................................................ [ 61%]
........................................................................ [ 64%]
........................................................................ [ 67%]
........................................................................ [ 70%]
........................................................................ [ 73%]
........................................................................ [ 76%]
........................................................................ [ 79%]
........................................................................ [ 82%]
........................................................................ [ 84%]
........................................................................ [ 87%]
........................................................................ [ 90%]
........................................................................ [ 93%]
........................................................................ [ 96%]
........................................................................ [ 99%]
......... [100%]
=============================== warnings summary ===============================
.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain-of-reese/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.14.7-final-0 ________________
Name Stmts Miss Cover
--------------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 52 0 100%
app/api/chat.py 224 1 99%
app/api/chats.py 110 0 100%
app/api/config.py 13 0 100%
app/api/doc_drafts.py 99 0 100%
app/api/docs.py 156 1 99%
app/api/git_sources.py 232 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 33 0 100%
app/api/sync.py 139 0 100%
app/api/tokens.py 40 0 100%
app/api/ui_settings.py 55 0 100%
app/config.py 210 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 124 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/errors.py 5 0 100%
app/core/logging.py 13 0 100%
app/core/rate_limit.py 44 0 100%
app/core/security_headers.py 20 0 100%
app/core/theming.py 38 0 100%
app/core/tokens.py 44 0 100%
app/db.py 22 0 100%
app/main.py 66 0 100%
app/models.py 128 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 317 1 99%
app/rag/archive_upload.py 134 0 100%
app/rag/chunker.py 206 4 98%
app/rag/doc_dates.py 18 0 100%
app/rag/folder_summaries.py 123 0 100%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 215 3 99%
app/rag/llm.py 244 1 99%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 192 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 329 0 100%
--------------------------------------------------
TOTAL 4147 16 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414).
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
validation OK
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+33
View File
@@ -35,6 +35,19 @@
--err-ink: #fca5a5; /* 9.3:1 on --err-bg */
--err-line: #ef4444; /* 4.6:1 on --err-bg (UI boundary, not text) */
/* Scrollbar tokens (phase 116, task 01) — the document modal's raw
code block scrolls horizontally (locked A1: code never wraps), so
its scrollbar must match the theme instead of the browser-native
light bar. Like the derived state colors above, COMPUTED in :root
from the identity palette via color-mix(): NOT stored, NOT
tab-controlled, NOT in BUILTIN_COLORS (theming.py) — every saved
theme repaints them. Muted thumb on a near-code-background track
(the .doc-raw background is --surface); the thumb holds >= 3:1
non-text contrast against the track (WCAG 2.1 AA — 3.7:1 at the
built-in palette). */
--scrollbar-thumb: color-mix(in srgb, var(--ink-soft) 60%, var(--surface));
--scrollbar-track: color-mix(in srgb, var(--surface) 75%, var(--bg));
--radius: 10px;
--radius-sm: 6px;
--shadow: 0 1px 2px rgb(0 0 0 / 0.30), 0 4px 16px rgb(0 0 0 / 0.35);
@@ -4330,6 +4343,26 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
overflow: auto;
padding: 1rem 1.25rem 1.5rem;
}
/* Themed horizontal scrollbar for the modal's raw code block (phase 116,
task 01): the .doc-raw pre already owns its overflow (its own
`overflow-x: auto` + `white-space: pre` — locked A1, code never
wraps), so the native light-gray bar a long line reveals is the pre's
own. Both engine families are themed, scoped to THIS modal only (no
global scrollbar restyle — the TODO's scope): scrollbar-color / -width
for Firefox, the ::-webkit-scrollbar pseudos for Chromium (height 8px
= the horizontal bar; the pre does not scroll vertically). The tokens
follow the saved theme via the :root color-mix derivation; the muted
thumb keeps >= 3:1 non-text contrast on the near-surface track. */
.doc-modal .doc-raw {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}
.doc-modal .doc-raw::-webkit-scrollbar { height: 8px; }
.doc-modal .doc-raw::-webkit-scrollbar-track { background: var(--scrollbar-track); }
.doc-modal .doc-raw::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 4px;
}
.doc-modal-loading { margin: 1.5rem auto; text-align: center; color: var(--ink-soft); }
/* No motion under reduced motion (same pattern as the phase-25 layers). */
+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