phase: 85_mobile_menu_gate_overlap

All verification complete. Final report:

**Phase 85 final verification pass — all green** (tasks 01–03 already complete; independently re-verified from scratch)
- Verified fix in tree: `styles.css` `.auth-gate` z-index 500→15 + rewritten stacking comment (no `z-index: 500` left); `index.html`/`document.html` comment-only; `token-gate.js` docstring-only (logic byte-identical); `test_mobile_hamburger_nav.py` real-click conversion + new `test_anonymous_toggle_tappable_with_gate_up`; new `tests/unit/test_gate_header_stacking.py` (3 pins); `test_api_tokens.py` untouched
- `uv run pytest` → 1717 passed, 1 warning (exit 0)
- `uv run pytest --cov=app --cov-report=term-missing` → TOTAL **99%** (>90%)
- E2E in isolation: `test_mobile_hamburger_nav.py` **8 passed**; `test_api_tokens.py` **9 passed** (unchanged); `test_smoke.py` **3 passed**; `test_document_viewer.py` **7 passed**
- `uv run ruff check . && uv run pyright` → clean / 0 errors
- Live probe (375×812, anonymous, fresh server): on `/` and `/history.html` `elementFromPoint` at toggle → SVG `path`, never `#auth-gate`; real click opens menu (`aria-expanded=true`); exactly one visible nav link (Chat) + sign-in copy; Chat link topmost over gate; `#main` inert — criteria 1, 2, 3 confirmed directly
- Criteria: (1) real-click menu on / + /history ✅ (2) dropdown above gate ✅ (3) anonymous contents + #main inert ✅ (4) admin byte-identical (phase-46 tests green) ✅ (5) doc-viewer gate under bar (CSS pin + doc-viewer suite) ✅ (6) full gate green ✅ (7) diff scoped to the 5 code files + new unit test + phase files, nothing in `app/` ✅ (8) commit + todo→complete move left to harness per executor rules (task files already in `complete/`)
- Note: `.agents/remediation_plan.md` (untracked) is a pre-existing artifact of the earlier security audit — left untouched; a missing "N passed" line seen mid-pass was my own `-q`+addopts `-q` = `-qq` quirk, not a defect
- Next pending phase: `86_history_page_width`
This commit is contained in:
2026-09-08 01:56:37 -04:00
parent f4150421bb
commit 412a560348
24 changed files with 692 additions and 57 deletions
+16 -11
View File
@@ -1631,23 +1631,28 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
/* Phase 79 (task 05): the in-app token gate — the gate surface of the
two token-only pages (the shell + the document viewer, one shared
.auth-gate markup each). A body-level FIXED overlay (the
body-level doc-modal precedent): while visible it is the ONLY
interactive surface — assets/token-gate.js locks the root (#main,
inert) so focus and keyboard traversal stay inside the gate
(WCAG, the inert-pair contract).
Stacking: z-index 500 — above the app content (the sticky header is
20, the skip-link 100) but BELOW the doc-modal (1000), which can
only be opened from the unlocked app anyway. Solid --bg: the canvas
+ grid texture live on <html> (the body stays transparent), so the
overlay reads as the app's own surface — no blur (the phase-08
perf anchor).
body-level doc-modal precedent): while visible it is the only
CONTENT-level interactive surface — assets/token-gate.js locks the
root (#main, inert) so focus and keyboard traversal stay inside
the gate (WCAG, the inert-pair contract).
Stacking (phase 85, TODO.md L3): z-index 15 — ABOVE all app
content (static) but BELOW the sticky header (20) and its mobile
dropdown (21, inside the header's stacking context): the
unauthenticated visitor keeps a reachable hamburger + menu while
the gate still covers and locks the app content (#main stays
inert — the lock is JS, not z-order — phase 79). The skip-link
(100) and the 1000-family overlays (doc-modal, sync backdrop) are
unaffected — all above 15; the doc-modal can only be opened from
the unlocked app anyway. Solid --bg: the canvas + grid texture
live on <html> (the body stays transparent), so the overlay reads
as the app's own surface — no blur (the phase-08 perf anchor).
The centered card reuses the #sources-gate visual language
(surface card + hairline + glyph + heading + sub + action) in a
tighter column. */
.auth-gate {
position: fixed;
inset: 0;
z-index: 500;
z-index: 15;
display: flex; /* the card is the only in-flow child — margin: auto centers it */
overflow-y: auto; /* short viewports: the card scrolls fully into view */
background: var(--bg);
+7 -3
View File
@@ -193,9 +193,13 @@ export async function mountGate(lockRoot, onAuthed) {
/* Anonymous: reveal the gate (drop hidden AND inert — the inert-pair
contract), lock the app, focus the token input. The gate is the
only interactive surface while visible: the lock root is inert,
so Tab never reaches the locked app (the composer on the shell,
the content on the viewer). */
only CONTENT-level interactive surface while visible: the lock
root is inert, so Tab never reaches the locked app (the composer
on the shell, the content on the viewer). The header was never
the lock root (and is never inert) — it was only visually
covered pre-phase-85: the gate's z-index (15) now sits below the
header (20), so the mobile hamburger + menu stay reachable while
the app content stays inert-locked (TODO.md L3). */
if (gate) {
gate.hidden = false;
gate.inert = false;