chore(agent): phase roadmap from TODO.md, 2 phases

Convert the two TODO items into append-mode phases (Protocol B):
18_nav_hamburger (TODO L3) and 19_ups_events_expandable (TODO L4),
numbered after the existing todo phases. TODO.md is cleared to an
empty list; its items now live in .agents/phases/todo/.

LLM-Generated: true
This commit is contained in:
2026-10-20 10:37:30 -04:00
parent cffbac5771
commit 4b0f53c499
11 changed files with 381 additions and 3 deletions
@@ -0,0 +1,36 @@
# Phase 18 — Collapse the nav bar into a hamburger
**Source:** `TODO.md` L3 — "The nav bar needs to collapse into a hamburger like brain of reese does"
**Story:** n/a
**Context:** the seven mastheads that carry the tab nav — `src/dashboards/web/pages/{index,system,disks,llm,ups,settings,certs}.html` (each has `nav class="tabs" aria-label="Dashboards"` with the five dashboard links; `login.html` deliberately has no tabs — it renders only the brand link and the theme toggle, and stays untouched), `src/dashboards/web/static/app.js` (`Dash.initChrome()`, called from the one `DOMContentLoaded` at line 786 — the `themeButton`/`clock`/`nav-status` blocks there are the per-element `if (element)` no-op pattern this phase's binding follows), `src/dashboards/web/static/dash.css` (`.masthead` at line 95 — `position: sticky; z-index: 40; height: var(--header-h)`, `nav.tabs` at line 131, the existing `@media (max-width: 640px)` block at line 286, the `prefers-reduced-motion` block at line 938), and the reference implementation the TODO names — `brain_of_reese` (sibling repo, read-only): `frontend/assets/header.js` lines 326–366 (the phase-46 binding: `#nav-toggle` shown ≤640px, `setNavMenu(open)` funnel, close on link click / Esc with focus return / `matchMedia` resize-back). Pinned test neighbours: `tests/test_api.py::test_the_disks_tab_is_in_the_masthead_of_every_page` (line 3837) and the served-page orphan-`id` audit in `tests/test_auth_api.py` (line 1654)
## Objective
On viewports ≤640px the masthead's five tab links collapse into a dropdown opened by a hamburger button, mirroring brain of reese's phase-46 behaviour: one shared binding in `app.js` (a no-op on any page without the elements — `/login`), the inline nav hidden and the dropdown absolutely positioned under the sticky masthead, and the desktop masthead byte-identical in behaviour above the breakpoint. The `masthead-right` cluster stays visible on phones; only the tab nav collapses.
## Dependencies
- — (none; builds on the shipped masthead, `initChrome`, and the 640px media block)
## Tasks
1. `01_hamburger_markup.md` — the `#nav-toggle` button and the `nav` id, identical across the seven tabbed pages, absent from `/login`
2. `02_dropdown_css.md` — the ≤640px block: toggle visible, inline tabs hidden, `.is-open` dropdown under the masthead, two-row masthead
3. `03_toggle_binding.md` — the `initChrome` binding: toggle + `aria-expanded`, close on link click / Esc / resize-back
4. `04_tests_and_verification.md` — the pins (present on all seven, absent on `/login`, CSS and binding), the orphan-`id` audit, and the narrow-viewport browser verification with screenshots
## Testing & Quality
- Unit/integration: `tests/test_api.py` — the served-page pins (see task 04); the existing masthead/tab pins (`test_the_disks_tab_is_in_the_masthead_of_every_page`, `test_every_page_links_to_both_admin_pages`) and the `tests/test_auth_api.py` orphan-`id` audit pass untouched — the new `id`s are referenced by `aria-controls`, the stylesheet, and `app.js`, so the audit must still clear
- Coverage: **>90%** on new/modified code (the phase is markup + CSS + one small JS binding; the coverage gate applies to the changed Python test files)
- The gate (`bash .agents/validate.sh`) includes `node --check` of `web/static` — the modified `app.js` must pass it
## Documentation & Consistency
- The final pass reviews `README.md` and `AGENTS.md` in full against the project as it stands after this phase — fixing every inconsistency found, **including pre-existing ones not caused by this phase**.
- AGENTS.md's `UI conventions` section gains the masthead-collapsing convention (the 640px breakpoint, the toggle's identity, the close paths, and that `/login` carries no toggle) so a future masthead edit knows the contract. The README's page table is unchanged — the nav is the same five links at every width.
## Completion Criteria
- [ ] at ≤640px: the tab links are hidden, the hamburger is visible, and clicking it opens a dropdown containing the five links; above 640px the desktop masthead is unchanged
- [ ] the dropdown closes on link click, on Esc (focus returns to the button), and on resize back above 640px; `aria-expanded` tracks the state
- [ ] `uv run pytest` green, coverage not down, `ruff` + `pyright` clean
- [ ] `bash .agents/validate.sh` green (including `node --check` of the modified `app.js`)
- [ ] narrow-viewport screenshots (closed, open, and one desktop no-regression shot) in `data/logs/` against the running dev server (invariant 10)
- [ ] no behavior change in completed work: the desktop masthead and every page render identically at widths >640px