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:
@@ -0,0 +1,55 @@
|
||||
# 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
|
||||
|
||||
1. `src/dashboards/web/static/app.js` — inside `initChrome()` (line 620), after the `nav-status` block and before the `return`, add a block in the file's per-element guard style (the `themeButton` pattern at line 626 is the shape: `if (element) { … }`):
|
||||
```js
|
||||
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);
|
||||
}
|
||||
```
|
||||
Notes: the breakpoint literal must match task 02's CSS block — if one drifts, the menu opens into a display:none nav. `ASSUMPTION:` that match is pinned by task 04's served-asset test (assert the `640px` literal appears in both the served stylesheet's nav block and the served `app.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).
|
||||
2. No `body`-level marker class (task 02's assumption carries: nothing on this app competes with the dropdown for taps, so brain's `nav-menu-open` marker is not copied).
|
||||
3. No change to the login page: `initChrome` already ships to it, and the `if (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 --check` passes); 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 and `aria-expanded` reads `true`; 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 stale `is-open`)
|
||||
- [ ] on `/login` the script loads and the guard no-ops (no console error, `node --check` clean)
|
||||
- [ ] `uv run pytest -q` green; `bash .agents/validate.sh` green (includes `node --check` of the modified `app.js`)
|
||||
Reference in New Issue
Block a user