phase: 116_modal_scrollbar_theme
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:
@@ -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.
|
||||
+103
@@ -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
|
||||
+18
@@ -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)
|
||||
+103
@@ -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
|
||||
+15
@@ -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.
|
||||
+103
@@ -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 |
@@ -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). */
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user