**Phase 18 — collapse the nav bar into a hamburger: complete, all green.**
Verified (criteria):
- ≤640px: the tab links leave the bar, the ☰ is visible, and it opens a dropdown
under the sticky masthead (`top: 100%`, `z-index: 41`, viewport-wide) with the
five stacked 40px links; above 640px the one-row desktop masthead is unchanged
and the button hidden — `data/logs/nav-task04-verify.cjs` 24/24 and
`data/logs/nav-final-pass.cjs` 45/45; screenshots
`data/logs/nav-hamburger-{closed,open}-360.png`, `data/logs/nav-masthead-desktop-1280.png` ✓
- The four close paths: the button again, a tab click (navigates and closes),
Esc (focus back on the opener), and `matchMedia` resize back above the
breakpoint (drops a stale `.is-open`; `aria-expanded` tracks state on every
path through the single `setNavMenu()` funnel) ✓
- `uv run pytest` → 1454 passed, 15 skipped; `bash .agents/validate.sh` → exit 0
(coverage 98.94% ≥ 94, ruff clean, pyright 0 errors, `node --check` + hygiene
clean); served-page pins: button on all seven tabbed pages and nowhere on
/login, CSS and binding compared on the 640px literal ✓
Real-device finding (mobile Chrome): the emulated 360px pass was blind to the
system top-edge zone — Android Chrome is edge-to-edge by default, so the sticky
bar started under the status bar and the first-row ☰ sat in a region the device
does not deliver taps to (unreachable, not unbound; the same symptom brain of
reese fixed in production). The ≤640px block now pads the bar by
`env(safe-area-inset-top, 0px)` (the block's `padding-block` value restated on
the top side), verified on the operator's phone; `env()` resolves to 0 on every
non-notch surface, so all other widths render byte-identical. The served-byte
pin's rule loop carries the line; CDP's `safeAreaInsets` override does not feed
`env()`, which is the honest limit of the emulated proof.
Docs: AGENTS.md *UI conventions* masthead entry extended with the three pins,
the drift-comparison, the safe-area rule, and the real-device verification
caveat; README's `web/` bullet names the ≤640px collapse with the desktop
masthead unchanged above the breakpoint.
Deviations: pinned the shipped token shape (`#nav-toggle { display:
var(--nav-toggle-display) }` + the `:root` token) instead of the task file's
literal `.nav-toggle { display: none }` — that literal loses to
`button.ghost { display: inline-flex }` (task 01 measured it); the test
docstring says so, so nobody "restores" the broken shape. `uv pip list
--outdated` still advisory (fastapi 0.153.0 → 0.156.2); this phase moved no
dependency. NOTES.md (emptied) / TODO.md (operator items added) are
harness-side changes — left in the working tree, as in phase 17.
Next pending phase: `.agents/phases/todo/19_ups_events_expandable/`.
LLM-Generated: true
3.0 KiB
3.0 KiB
Task 01 — The hamburger button markup
Phase: 18_nav_hamburger · Source: TODO.md:3 — "The nav bar needs to collapse into a hamburger like brain of reese does"
Story: n/a
Objective
The seven pages that carry the tab nav each gain the hamburger button and the nav id, so the (later task's) CSS and binding have stable anchors to work on. The markup is identical on every page, and /login — which deliberately renders only the brand link and the theme toggle — stays untouched.
Work
src/dashboards/web/pages/index.html·system.html·disks.html·llm.html·ups.html·settings.html·certs.html— in each masthead, immediately beforenav class="tabs", add the toggle:and give the nav its id:<button class="ghost nav-toggle" id="nav-toggle" type="button" aria-expanded="false" aria-controls="nav-tabs" aria-label="Toggle navigation">☰</button><nav class="tabs" id="nav-tabs" aria-label="Dashboards">. The button sits between.brandandnav.tabs, inside the same<header class="masthead">— one element per page, no script changes in this task.src/dashboards/web/static/dash.css— add the one base rule beside the otherbutton.ghost, a.ghostfamily (the masthead section):.nav-toggle { display: none; }— the button is invisible on every width until task 02's ≤640px block shows it, so the project stays exactly as it looks today after this task.src/dashboards/web/pages/login.html— no change. Verify bygrep -c 'id="nav-toggle"'returning 0 (the pin lands in task 04).ASSUMPTION:the TODO named brain of reese's behaviour, not its breakpoint or glyph. This task takes the 640px breakpoint (brain's(max-width: 640px)anddash.css's existing 640px block) and the☰glyph witharia-label="Toggle navigation"; the CSS task owns the visual.ASSUMPTION:only the tab nav collapses on phones. Themasthead-rightcluster (#nav-statusdots, pause,⚷ Certs,⚙ Account, theme toggle) stays visible; the masthead wraps to two rows at ≤640px (task 02).
Testing & Quality
- Integration: the served pages still pass the untouched masthead pins (
test_the_disks_tab_is_in_the_masthead_of_every_page,test_every_page_links_to_both_admin_pages) and the orphan-idaudit —#nav-toggleis referenced by the (task 02) stylesheet rule andaria-controls, so the audit must not flag it - Coverage: >90% on modified code (HTML only — the gate's
node --checkand pytest suite are the checks)
Completion Criteria
grep -l 'id="nav-toggle"' src/dashboards/web/pages/*.htmllists exactly the seven tabbed pages, neverlogin.html- every one of the seven carries the identical button block and the
navid (diff of the blocks across pages is empty) uv run pytest -qgreen;bash .agents/validate.shgreen- no behavior change: at every width the pages render exactly as before after this task (the button is
display:nonefrom the base rule shipped with it)