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:
@@ -0,0 +1,55 @@
|
||||
# Phase 85 — Keep the mobile menu reachable while the token gate is up
|
||||
|
||||
**Source:** `TODO.md` L3 — "Only on the chat page, and only when navigating there directly, does the hamburger menu on mobile not work. It works on every other page, but clicking it from the chat page does not expand the menu. This makes the menu inaccessible on mobile. Note that when logging in the hamburger menu works as expected, so it's something specifically about the chat page that breaks it."
|
||||
**Story:** n/a (owner bug report — `TODO.md` L3, 2026-09-07)
|
||||
**Context:** `frontend/assets/token-gate.js` (phase 79 task 05 — `mountGate(lockRoot, onAuthed)`: anonymous → the gate is revealed and `lockRoot.inert = true` (`#main`); the phase-79 contract line "the gate is the only interactive surface; the header is locked out with the rest of the page"), `frontend/index.html` (the body-level `<section class="auth-gate" id="auth-gate" hidden inert>` after `#main` — the shell's ONE gate; the six navbar views all share it) + `frontend/document.html` (the same markup as `#doc-auth-gate`), `frontend/assets/styles.css` (`.auth-gate { position: fixed; inset: 0; z-index: 500; … }` — the stacking comment above the rule; `.app-header` is `position: sticky; z-index: 20` (global rule near the top of the file); the mobile dropdown `.app-nav.is-open` carries `z-index: 21` INSIDE the header's stacking context in the `@media (max-width: 640px)` block; the skip-link sits at `z-index: 100`, overlays at 1000 — none of those are affected), `frontend/assets/header.js` (the module-level hamburger binding — phase 46: `#nav-toggle` click toggles `#app-nav .is-open` + `aria-expanded`; Esc/link/resize close; the binding is independent of the gate and of `initSharedHeader`), `tests/e2e/test_mobile_hamburger_nav.py` (phase 46 suite — its `_js_open_menu` helper exists precisely because "a real click on #nav-toggle is intercepted by the gate" for anonymous visitors), `tests/e2e/test_api_tokens.py` (phase 79 suite — no hamburger pins; the gate contract pins stay).
|
||||
|
||||
## Bug basis (confirmed by live reproduction, 2026-09-07 — headless Chromium, 375×812)
|
||||
- **Anonymous visitor, any shell view** (`/`, `/tuning.html`, `/history.html`): the token gate is a full-viewport overlay (`position: fixed; inset: 0; z-index: 500`) that sits ABOVE the sticky header (`z-index: 20`). `document.elementFromPoint` at the `#nav-toggle` center returns `#auth-gate` in every case — a real tap on the hamburger is intercepted by the gate, and a Playwright real click times out (the click never reaches the button). The menu is therefore **inaccessible on mobile for every unauthenticated visitor**.
|
||||
- **Signed-in admin, same viewport:** the gate ships hidden and the hamburger works on every view — direct loads of `/`, `/tuning.html`, `/history.html` and the SPA switch tuning→chat all open the menu with a real click (verified). The owner's "only on the chat page" perception is the entry point: the chat page is where a visitor lands, meets the gate, and finds the dead hamburger.
|
||||
- **`login.html`** is a separate document with no gate (phase 79 left it untouched) — the toggle is tappable there. This matches the owner's "when logging in the hamburger menu works as expected".
|
||||
- The gate landed in phase 79 (2026-09-07 — the same day this TODO was written); before it, anonymous visitors met the soft-gated chat page and the hamburger worked everywhere.
|
||||
|
||||
## Objective
|
||||
An unauthenticated visitor who meets the token gate can still open the mobile menu: the gate stays the app's lock (`#main` inert, the APIs token-gated) but no longer physically covers the header — the hamburger and its dropdown remain tappable and render above the gate, and the anonymous menu shows exactly what it always showed (the Chat link + the sign-in copy — the whoami ship-hidden contract untouched). Nothing new is exposed: the only tappable header controls for an anonymous visitor are the hamburger (menu: Chat + sign in) and the brand.
|
||||
|
||||
## Owner decisions (chat, 2026-09-07 — confirmed with the roadmap, recorded per AGENTS.md rule 3)
|
||||
- **A1 — the fix is pure CSS stacking.** `.auth-gate` moves from `z-index: 500` to **`z-index: 15`** — below the header's 20, so the header (and the open dropdown's 21, inside the header's stacking context) paint above the gate and taps on the bar reach the bar. The gate keeps `position: fixed; inset: 0` (it still covers and locks the app content — `#main` stays `inert`; the token card stays the only CONTENT-level interactive surface). This **revises the phase-79 locked line** "the gate is the only interactive surface; the header is locked out with the rest of the page" — the header is no longer locked out, at the owner's request (the TODO item). No JS behavior in `token-gate.js` changes: mount, silent re-auth, role check, focus, lock/unlock are all byte-identical.
|
||||
- **A2 — the document viewer's gate gets the same treatment.** `#doc-auth-gate` (document.html) is the same `.auth-gate` class, so the one CSS rule covers it: the viewer's row-1 bar (the same `.app-header` class) stays tappable while its gate is up. No separate markup or rule.
|
||||
- **A3 — docstrings/tests follow the new contract.** Every docstring and test helper that asserts the gate "covers the header" / "intercepts the toggle" is updated to the revised contract (header tappable, menu reachable, `#main` still inert). The phase-46 E2E's `_js_open_menu` programmatic drive becomes a REAL click for anonymous visitors.
|
||||
|
||||
## Design (shared by all tasks — the executor reads this, not the chat)
|
||||
- **`frontend/assets/styles.css`** — ONE rule changes: in the `.auth-gate` rule (the stacking comment above it says "z-index 500 — above the app content (the sticky header is …)") the value becomes `z-index: 15`, and the comment is rewritten: the gate (15) sits ABOVE the app content (static) but BELOW the sticky header (20) and its mobile dropdown (21 inside the header's stacking context) — the phase-85 revision of the phase-79 "gate covers the header" contract: an unauthenticated visitor keeps a reachable menu while `#main` stays inert-locked. The skip-link (100) and the 1000-family overlays (modals, doc-modal, sync backdrop) are unaffected (all above 15).
|
||||
- **`frontend/index.html` / `frontend/document.html`** — if the gate section's HTML comments assert the overlay covers the header, update the wording to the new stacking (comment-only — the markup, ids, classes, and the ship-hidden `hidden inert` pair are untouched).
|
||||
- **`frontend/assets/token-gate.js`** — docstring only: the module header's description of the anonymous state ("the gate is revealed … the locked app must not receive focus or keyboard traversal") keeps its `#main`-locking language; any line asserting the header is covered/locked is reworded (the lock root is and always was `#main` — the header was never `inert`; it was only visually covered).
|
||||
- **`tests/e2e/test_mobile_hamburger_nav.py`** — `_js_open_menu` becomes a real click (the anonymous contract: `page.click("#nav-toggle")` opens the menu exactly as for admin); the helper docstring and the four test docstrings that cite the interception are updated (phase-85: the gate no longer intercepts the toggle — the menu is the header's, the gate covers only `#main`); a NEW test pins the exact TODO regression: anonymous at 375px, `document.elementFromPoint` at the toggle center resolves to the toggle (or a child of it), never `#auth-gate`, and the real click opens the menu (`aria-expanded="true"`, `.is-open`, opacity 1) with the anonymous contents (exactly one visible nav link — "Chat" — the admin links ship-hidden inside the menu, the sign-in copy visible).
|
||||
- **`tests/e2e/test_api_tokens.py`** — no hamburger pins exist (verified by grep: no `nav-toggle` / `intercept` references); the gate contract pins (gate visible for anonymous, hidden + `#main` unlocked after a valid token, wrong-token error, revocation) must stay green UNCHANGED — this phase's E2E gate runs this suite in isolation as the regression proof that the stacking change broke nothing about the lock.
|
||||
- **Not touched:** `header.js` (the binding is correct — the button was never unbound; it was unreachable), the gate's markup/ids, the whoami ship-hidden contract, the `#main` inert contract, everything server-side.
|
||||
|
||||
## Dependencies
|
||||
- `84_docs_push_error_sanitization` (todo) — pipeline predecessor (execution order) only; NO code dependency: this phase touches frontend CSS/comments, two E2E suites, and nothing in `app/`.
|
||||
|
||||
## Tasks
|
||||
1. `01_gate_below_header.md` — the z-index restack + comment/docstring alignment + the source-level unit pin.
|
||||
2. `02_e2e_pin_updates.md` — the phase-46 suite's real-click conversion + the new TODO-regression test + the docstring sweep.
|
||||
3. `03_verify_and_commit.md` — full gate (unit + integration + coverage, BOTH E2E suites in isolation, smoke, ruff + pyright) + atomic commit.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit — new source-level pin (house pattern — read the CSS text, no browser): the `.auth-gate` rule carries `z-index: 15` and the `.app-header` rule carries `z-index: 20` (the "gate below the header" contract — the regression that put the gate at 500 must not return). Lives in the new `tests/unit/test_gate_header_stacking.py` (or an existing CSS-reading unit module if the executor finds one — the `tests/unit/test_hamburger_nav.py` style).
|
||||
- E2E (isolation gates per AGENTS.md rule 9): `uv run pytest tests/e2e/test_mobile_hamburger_nav.py -v --no-cov` green in isolation (the phase's own suite — real clicks for anonymous now) AND `uv run pytest tests/e2e/test_api_tokens.py -v --no-cov` green in isolation (the phase-79 lock contract unchanged) AND `uv run pytest tests/e2e/test_smoke.py -v --no-cov` green.
|
||||
- Coverage: **>90%** on `app/` — no `app/` code changes in this phase (the floor is held by the untouched suite).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] Anonymous at 375×812 on `/` (and `/history.html`): a REAL click on `#nav-toggle` opens the menu (the TODO item's exact scenario — "clicking it … does not expand the menu" is fixed); `elementFromPoint` at the toggle center never returns `#auth-gate`.
|
||||
- [ ] The open dropdown renders ABOVE the gate (z-order pin in the E2E: with the gate up and the menu open, the menu's computed z context sits above the gate's 15 — probed via the menu being visible + clickable link targets).
|
||||
- [ ] The anonymous menu contents are unchanged: exactly one visible nav link (Chat) + the sign-in copy; admin links ship-hidden; the gate card (token input + Sign in + "Sign in as admin") is untouched and still the only content-level interactive surface (`#main` `inert` — phase-79 pins green).
|
||||
- [ ] Signed-in admin behavior is byte-identical (phase-46 suite green — the admin tests already used real clicks).
|
||||
- [ ] The document viewer's gate (`#doc-auth-gate`) sits below the viewer's bar too (same rule — covered by the CSS pin; `tests/e2e/test_document_viewer.py` stays green).
|
||||
- [ ] `uv run pytest` green; `uv run pytest --cov=app --cov-report=term-missing` >90%; both E2E suites + smoke green in isolation; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] `git diff --stat` limited to `frontend/assets/styles.css`, `frontend/index.html` (comments only), `frontend/document.html` (comments only), `frontend/assets/token-gate.js` (docstring only), the two E2E files, the new unit test, phase files — nothing in `app/`.
|
||||
- [ ] One atomic `--no-gpg-sign` commit (e.g. `fix(ui): keep the mobile menu reachable while the token gate is up`); phase dir moved to `.agents/phases/complete/`.
|
||||
|
||||
## Locked decisions
|
||||
- **`#main` stays inert while the gate is up** (A1) — the phase-79 WCAG lock contract (the locked app must not receive focus or keyboard traversal) is intact; only the header's VISUAL lock (the overlay covering it) is lifted.
|
||||
- **No gate markup/behavior change** — `token-gate.js` logic is byte-identical; the phase-79 E2E suite (token gate, revocation, wrong-token, silent re-auth) passes unchanged.
|
||||
- **One CSS value is the whole fix** — `z-index: 500 → 15` on `.auth-gate`; no new elements, no new classes, no JS.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Task 01 — Restack the gate below the header
|
||||
|
||||
**Phase:** `85_mobile_menu_gate_overlap` · **Source:** `TODO.md:3` — "Only on the chat page, and only when navigating there directly, does the hamburger menu on mobile not work. … This makes the menu inaccessible on mobile. Note that when logging in the hamburger menu works as expected, so it's something specifically about the chat page that breaks it."
|
||||
**Story:** n/a (owner bug report — `TODO.md` L3)
|
||||
|
||||
## Objective
|
||||
The gate no longer physically covers the header: one z-index value moves it from above the sticky bar to below it, so the hamburger and its dropdown are tappable (and paint correctly) for every unauthenticated visitor, while the gate still covers and locks the app content.
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/styles.css` — in the `.auth-gate` rule (find it via the stacking comment "z-index 500 — above the app content"; the rule sets `position: fixed; inset: 0`) change `z-index: 500` → `z-index: 15`. Rewrite that comment block to state the phase-85 stacking contract: **15** (gate) < **20** (`.app-header`, sticky) — the bar and the mobile dropdown (**21**, inside the header's stacking context) paint above the gate; the skip-link (**100**) and the **1000**-family overlays (doc-modal, sync backdrop, the `.auth-gate`'s former neighborhood) are unaffected; the gate still covers all app CONTENT (static) and `#main` stays `inert` (the lock is JS, not z-order — phase 79).
|
||||
2. `frontend/index.html` — the gate section's comment (the block above `<section class="auth-gate" id="auth-gate" hidden inert>`): if it asserts the overlay covers the header, reword to the new contract (the gate covers `#main`'s content region visually; the header stays reachable — phase 85, TODO.md L3). Comment-only; the markup, ids, classes, and the `hidden inert` ship-hidden pair are byte-identical.
|
||||
3. `frontend/document.html` — the same comment alignment for `#doc-auth-gate` (same class, same rule — A2). Comment-only.
|
||||
4. `frontend/assets/token-gate.js` — module docstring sweep: keep every `#main`-locking sentence (the inert contract is unchanged); reword any sentence asserting the header is covered/locked-out (it was only visually covered — the lock root was and is `#main`). Logic byte-identical.
|
||||
5. `tests/unit/test_gate_header_stacking.py` (new — house source-level pattern, the `tests/unit/test_hamburger_nav.py` style: read `frontend/assets/styles.css` as text, no browser):
|
||||
- the `.auth-gate` rule's declaration block carries `z-index: 15` (regex on the rule text between the selector and its closing brace);
|
||||
- the `.app-header` rule carries `z-index: 20` (the bar must stay above the gate);
|
||||
- the mobile dropdown rule (`.app-nav.is-open` in the `max-width: 640px` block) keeps `z-index: 21` (the open menu paints above the bar content).
|
||||
- A short module docstring cites the bug basis (the gate at 500 intercepted the toggle — TODO.md L3) so the pin's intent survives.
|
||||
6. `- ASSUMPTION: the value 15 is the choice (any integer below 20 and above the static app content works; 15 sits below the header's 20 with headroom and is pinned by the unit test — the executor does not pick a different number).`
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `uv run pytest tests/unit/test_gate_header_stacking.py -v` green (the three z-order pins).
|
||||
- No integration test needed (no `app/` change); the existing integration suite must stay green (`uv run pytest -q` as part of the phase gate in task 03).
|
||||
- Coverage: **>90%** on `app/` is unaffected (no `app/` lines change).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `grep -n "z-index: 15" frontend/assets/styles.css` shows the `.auth-gate` rule; `grep -n "z-index: 500"` no longer matches any `.auth-gate` declaration.
|
||||
- [ ] `uv run pytest tests/unit/test_gate_header_stacking.py -v` green.
|
||||
- [ ] `git diff --stat -- frontend/` limited to the four files above (CSS value + comments/docstrings only); the gate markup in both HTML files is byte-identical (`git diff frontend/index.html frontend/document.html` shows comment lines only); `token-gate.js` diff is docstring-only.
|
||||
- [ ] No behavior change in completed work (the phase-46/79 behavior pins are exercised in task 02/03).
|
||||
@@ -0,0 +1,31 @@
|
||||
# Task 02 — Convert the E2E pins to the real-click contract
|
||||
|
||||
**Phase:** `85_mobile_menu_gate_overlap` · **Source:** `TODO.md:3` — "… clicking it from the chat page does not expand the menu. This makes the menu inaccessible on mobile. …"
|
||||
**Story:** n/a (owner bug report — `TODO.md` L3)
|
||||
|
||||
## Objective
|
||||
The phase-46 hamburger suite drives the anonymous menu with REAL clicks (the programmatic workaround existed only because the gate intercepted the toggle — it no longer does), and a new test pins the exact TODO regression: an anonymous visitor's toggle is tappable and the menu opens above the gate.
|
||||
|
||||
## Work
|
||||
1. `tests/e2e/test_mobile_hamburger_nav.py` —
|
||||
- `_js_open_menu`: convert the body to a REAL click — `page.click("#nav-toggle")` (keep the three assertions: `aria-expanded="true"`, `.is-open` class, opacity 1). Rewrite the helper docstring: phase 85 — the gate (z 15) sits BELOW the header (z 20), so a real tap reaches the toggle for anonymous visitors too; the phase-79 "intercepted by the gate" workaround is retired.
|
||||
- Every test that called `_js_open_menu` (`test_anonymous_menu_contents`, `test_esc_and_outside_close`, `test_animation_and_reduced_motion` — both contexts) switches to the real-click helper. Keep the assertions EXACTLY as they are (anonymous menu = exactly one visible link "Chat", admin links hidden inside the menu, Esc closes + refocuses the toggle, outside click does NOT close — the gate overlay at point (10, 780) is still the "outside" surface for anonymous, the menu stays open, Esc settles it; the 180ms transition pins unchanged).
|
||||
- The module docstring's contract paragraph (the phase-79 sentence about the full-viewport overlay covering the header) is updated to the phase-85 stacking.
|
||||
- **NEW test `test_anonymous_toggle_tappable_with_gate_up`** (the TODO regression pin, placed after `test_anonymous_menu_contents`): anonymous at 375×812 on `/` with the gate up (wait for the settled anonymous state — the existing `_wait_settled_anonymous`):
|
||||
- `document.elementFromPoint` at the `#nav-toggle` center resolves to the toggle itself or a descendant of it (the SVG path) — assert it is NOT `#auth-gate` (this is the exact probe that returned `#auth-gate` in the bug reproduction);
|
||||
- a real `page.click("#nav-toggle")` opens the menu (`aria-expanded="true"`, `.is-open`, computed opacity 1) — and the menu is ABOVE the gate: the "Chat" link is visible and `is_visible()` true with the gate still up (the dropdown's z context (21 in the header's 20) paints over the gate's 15);
|
||||
- close with Esc (the settled-closed state) so the test leaves the page clean.
|
||||
- Add the new test to the module docstring's Test → story mapping list (numbered entry, one line: anonymous toggle tappable with the gate up — elementFromPoint never `#auth-gate`, real click opens the menu above the gate).
|
||||
2. `tests/e2e/test_api_tokens.py` — verify-only task: the suite carries NO hamburger pins (grep confirms no `nav-toggle`/`intercept` references). Run it in isolation and confirm green UNCHANGED — it is the regression proof that the restack did not alter the gate's lock contract (gate visible for anonymous, valid token → gate hidden + `#main` unlocked, wrong token → `role="alert"`, revocation). If any assertion unexpectedly fails because of the z-order (e.g. a "header is covered" style probe), update THAT assertion to the phase-85 contract (document it in the diff comment) — but do not weaken the lock assertions (`#main` inert, token flow) under any circumstance.
|
||||
3. `- ASSUMPTION: no new conftest machinery — the new test reuses the existing session `browser` fixture + the 375×812 page pattern (`_mobile_page`) and `_wait_settled_anonymous`; the suite header's run-in-isolation line is unchanged.`
|
||||
|
||||
## Testing & Quality
|
||||
- E2E (this task's gate): `uv run pytest tests/e2e/test_mobile_hamburger_nav.py -v --no-cov` green **in isolation** (DB up — the conftest `db_ready` fixture; the suite needs the mock LLM only for the login tests, which the conftest provides).
|
||||
- E2E (regression proof): `uv run pytest tests/e2e/test_api_tokens.py -v --no-cov` green **in isolation**, file unchanged (or with documented phase-85 assertion updates only, if item 2's verification demanded them).
|
||||
- Coverage: >90% on `app/` unaffected (test-only task).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `grep -n "querySelector('#nav-toggle').click()" tests/e2e/test_mobile_hamburger_nav.py` returns nothing (the JS-dispatched drive is gone); the helper uses `page.click("#nav-toggle")`.
|
||||
- [ ] `test_anonymous_toggle_tappable_with_gate_up` exists, pins the `elementFromPoint`-not-`#auth-gate` probe, and passes in isolation; the module docstring mapping lists it.
|
||||
- [ ] `uv run pytest tests/e2e/test_mobile_hamburger_nav.py -v --no-cov` and `uv run pytest tests/e2e/test_api_tokens.py -v --no-cov` both green in isolation.
|
||||
- [ ] No change to `app/`, `frontend/`, or conftest machinery.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Task 03 — Full gate + atomic commit
|
||||
|
||||
**Phase:** `85_mobile_menu_gate_overlap` · **Source:** `TODO.md:3` — the mobile-menu bug report (TODO.md L3)
|
||||
**Story:** n/a (owner bug report — `TODO.md` L3)
|
||||
|
||||
## Objective
|
||||
Run the complete phase gate, land the phase as one atomic commit, and move the phase directory to `complete/`.
|
||||
|
||||
## Work
|
||||
1. **Full regression gate** (AGENTS.md rule 9):
|
||||
- `uv run pytest` — unit + integration green.
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` — `app/` coverage **>90%** (no `app/` change this phase — the floor is held by the untouched suite; confirm the report).
|
||||
- `uv run pytest tests/e2e/test_mobile_hamburger_nav.py -v --no-cov` — green **in isolation** (this phase's E2E contract — the anonymous real-click menu above the gate).
|
||||
- `uv run pytest tests/e2e/test_api_tokens.py -v --no-cov` — green **in isolation** (the phase-79 gate lock contract — the restack's regression proof).
|
||||
- `uv run pytest tests/e2e/test_smoke.py -v --no-cov` — green in isolation.
|
||||
- `uv run ruff check . && uv run pyright` — clean.
|
||||
2. **Manual live check** (keep the output in the session log): start the dev server (`uv run uvicorn app.main:app`), open `http://localhost:8000/` at a 375px viewport (browser devtools device mode or the suite's own probe), NOT signed in: tap the hamburger → the menu opens above the gate (Chat + sign-in visible, the token card behind/below the bar); type a wrong token in the gate → the error line shows and the menu state is independent (Esc closes the menu, the gate stays). Sign in as admin → the menu behaves exactly as before. (The automated pins in task 02 cover this; the live check confirms the real-device-feeling stacking.)
|
||||
3. **Commit** (AGENTS.md rule 8 — one atomic, Conventional-Commits commit, always `--no-gpg-sign`), staging `frontend/assets/styles.css`, `frontend/index.html`, `frontend/document.html`, `frontend/assets/token-gate.js`, `tests/e2e/test_mobile_hamburger_nav.py`, `tests/e2e/test_api_tokens.py` (only if task 02 item 2 required documented assertion updates), `tests/unit/test_gate_header_stacking.py`, and the phase files:
|
||||
`fix(ui): keep the mobile menu reachable while the token gate is up`
|
||||
— body: TODO.md L3 — the phase-79 token gate (z 500, fixed full-viewport) sat above the sticky header (z 20), so an unauthenticated visitor's tap on the mobile hamburger hit the gate overlay and the menu was unreachable until login (login.html is gate-free, which is why "logging in" worked). The gate now sits at z 15 — below the header and its mobile dropdown (21) — while `#main` stays inert-locked and the gate's markup/behavior is byte-identical. Phase-46 suite converted to real clicks + a new pin (elementFromPoint at the toggle never resolves to the gate); phase-79 suite green unchanged.
|
||||
4. Move the phase directory: `mv .agents/phases/todo/85_mobile_menu_gate_overlap .agents/phases/complete/` and include the move in the same commit.
|
||||
|
||||
## Testing & Quality
|
||||
- This task IS the phase-level gate — the commands above are the completion evidence.
|
||||
- Coverage: >90% held.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] All five gate commands green (unit + integration, coverage >90%, both E2E suites in isolation, smoke, ruff + pyright).
|
||||
- [ ] The live check shows the anonymous menu opening above the gate at 375px (output kept in the session log).
|
||||
- [ ] Exactly one new commit; `git show --stat HEAD` lists the staged files above + the phase files (todo → complete move) — nothing else (in particular `app/`, `pyproject.toml` / `uv.lock` untouched).
|
||||
- [ ] `.agents/phases/complete/85_mobile_menu_gate_overlap/` exists; `todo/` no longer contains it.
|
||||
Reference in New Issue
Block a user