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
4.1 KiB
4.1 KiB
Task 03 — The toggle binding in initChrome
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
One shared binding in app.js (the file every page loads, including /login) that makes the button work on all seven tabbed pages and is a no-op where the elements are absent. The behaviour mirrors brain of reese's phase-46 binding: a setNavMenu(open) funnel so no close path can leave the state stuck, aria-expanded honest at all times, close on link click, on Esc (focus returns to the button), and on resize back above the breakpoint.
Work
src/dashboards/web/static/app.js— insideinitChrome()(line 620), after thenav-statusblock and before thereturn, add a block in the file's per-element guard style (thethemeButtonpattern at line 626 is the shape:if (element) { … }):Notes: the breakpoint literal must match task 02's CSS block — if one drifts, the menu opens into a display:none nav.const navToggle = $('#nav-toggle'); const appNav = $('#nav-tabs'); if (navToggle && appNav) { const setNavMenu = (open) => { appNav.classList.toggle('is-open', open); navToggle.setAttribute('aria-expanded', open ? 'true' : 'false'); }; navToggle.addEventListener('click', () => setNavMenu(!appNav.classList.contains('is-open'))); // A link click navigates — shut the menu (event-delegated: the five // links are static markup, so one listener on the nav covers all of them). appNav.addEventListener('click', (event) => { if (event.target.closest('a')) setNavMenu(false); }); // Esc closes while open; focus returns to the opener. document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && appNav.classList.contains('is-open')) { setNavMenu(false); navToggle.focus(); } }); // Resize back to desktop: the inline nav reappears — drop the open state // so aria-expanded stays honest (the CSS hides the dropdown above 640px // regardless, but a stuck `is-open` would re-open on the next shrink). const mq = window.matchMedia('(max-width: 640px)'); const onMqChange = () => { if (!mq.matches) setNavMenu(false); }; if (mq.addEventListener) mq.addEventListener('change', onMqChange); }ASSUMPTION:that match is pinned by task 04's served-asset test (assert the640pxliteral appears in both the served stylesheet's nav block and the servedapp.js), not by a shared constant — the file's convention is per-file literals (the 640px in CSS, 900px for the clock, and the page scripts' own numbers do not share a constant either).- No
body-level marker class (task 02's assumption carries: nothing on this app competes with the dropdown for taps, so brain'snav-menu-openmarker is not copied). - No change to the login page:
initChromealready ships to it, and theif (navToggle && appNav)guard is the no-op — verify in task 04 that/login's served script is the same shared file (it is: every page loads/static/app.js) and the guard makes the binding inert there.
Testing & Quality
- Integration: the binding's behaviour is verified in the browser (task 04 — the repo has no JS test runner; the pins are structural on the served bytes: the block is present in the served
app.js, the four close paths named in a comment,node --checkpasses); the untouched Python suite stays green - Coverage: >90% on modified code (one JS function; the Python coverage gate applies to the test files task 04 adds)
Completion Criteria
- at 360px: clicking
☰opens the dropdown andaria-expandedreadstrue; clicking again closes it; clicking a tab link closes it as the page navigates - with the menu open: Esc closes it and focus is on the
☰button; resizing to 700px closes it and the next shrink back to 360px leaves it closed (no staleis-open) - on
/loginthe script loads and the guard no-ops (no console error,node --checkclean) uv run pytest -qgreen;bash .agents/validate.shgreen (includesnode --checkof the modifiedapp.js)