Files
brain-of-reese/.agent/user_stories/mobile-hamburger-nav.md
T
ducoterra 02c76ad328 chore(agent): phase roadmap from TODO.md — 8 phases (40–47), 24 tasks
Converts the 9 TODO items into an executable phase roadmap (Protocol B,
appended after phase 39):

- 40 tuning toggle anonymous flash (TODO L3)
- 41 sync fail-fast + modal when a model is down (TODO L4)
- 42 no reply autoscroll (TODO L5)
- 43 thinking scroll back — user scroll + gated autoscroll (TODO L7)
- 44 markdown tables (TODO L6)
- 45 agent unlimited tool calls behind BOR_AGENT_MAX_ROUNDS (TODO L8)
- 46 mobile hamburger nav (TODO L9)
- 47 quadlet + jinja import formats, A9 revision (TODO L10–L11)

Each phase carries a user story, a dedicated Playwright E2E suite plan,
and owner-locked decisions (R1 A9 format extension, R2 phase-37 budget
revision, A1–A5 scope decisions) confirmed 2026-08-27.

Also records the completed phases 30–39 todo/ -> complete/ moves that
were pending in the working tree. TODO.md is cleared (items now live in
.agent/phases/todo/).
2026-08-27 18:25:53 -04:00

111 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Story: Mobile hamburger nav
**Phase:** `46_mobile_hamburger_nav` · **Source:** `TODO.md` L9 ·
**E2E:** `tests/e2e/test_mobile_hamburger_nav.py`
## Bug report (verbatim, `TODO.md` L9)
> "The navbar on mobile is way too squished. Make it a hamburger
> dropdown menu with a nice animation"
## Narrative
As **a mobile user**, the 58px header currently crams up to four text
nav pills (Chat / Sources / Git sources / Tuning) next to the brand and
four icon action pills — the phase-34/35 squeeze at 360–375px leaves
0.72rem-font pills that are hard to hit and hard to read. The nav links
move into a **hamburger dropdown menu** on small screens: one
`#nav-toggle` button in the bar, and the links open as an animated
panel below the header with comfortable touch targets.
- **Given** a viewport ≤640px
- **When** I tap the hamburger
- **Then** the nav menu drops down with a short slide+fade animation and
full-size links; tapping a link navigates and closes the menu.
- **Given** a viewport >640px
- **When** the page loads
- **Then** nothing changes — the inline nav pills render exactly as
today.
## Acceptance criteria
1. **Mobile (≤640px):** the inline nav pills are hidden from the bar; a
hamburger button (`#nav-toggle`, `aria-label="Menu"`,
`aria-controls="app-nav"`, `aria-expanded`) appears — 44px touch
target, icon-only.
2. **Menu:** `.app-nav` (now `id="app-nav"`) becomes a dropdown panel
below the header — vertical full-width rows, ≥44px targets, readable
font size; the **auth visibility contract is preserved inside the
menu** (anonymous: only "Chat"; admin: Chat / Sources / Git sources /
Tuning — the same ship-hidden `hidden` attributes the whoami gate
already drives).
3. **Animation:** opening/closing animates (slide-down + fade, ≈180ms);
`prefers-reduced-motion: reduce` stills it (no transition).
4. **Behavior:** toggle flips `aria-expanded`; `Esc` closes while open;
a link click navigates **and** closes the menu; resizing back to
>640px closes it (the inline nav reappears, no stale state).
5. **Bar layout:** the action pills (Tuning toggle, Sync, New chat,
Sign in/out) stay in the bar icon-only — the phase-35 tightest
squeeze rules on `.nav-link` / `.app-nav` gaps are replaced by the
roomier menu; the bar fits 360px with the brand intact or clipped as
today.
6. **All six pages** get the identical toggle + menu (the phase-34
"same bar on every page" contract).
## Owner-confirmed (2026-08-27, roadmap A5)
1. **The hamburger contains the nav links only** (Chat / Sources / Git
sources / Tuning). The action pills stay in the bar.
2. **Animation:** slide-down + fade, 180ms; `prefers-reduced-motion`
stills it.
3. **Breakpoint:** the existing ≤640px mobile block (no new breakpoint).
## UI Visualization & Structure
- **Markup (all six pages — `index.html`, `sources.html`,
`document.html`, `git-sources.html`, `login.html`, `tuning.html`):**
a `#nav-toggle` button inserted before `<nav class="app-nav"
aria-label="Primary">` (which gains `id="app-nav"`); the nav keeps
its existing links and `hidden` attributes byte-identically.
- **CSS** (`frontend/assets/styles.css`, the `@media (max-width: 640px)`
block): `.nav-toggle { display: none }` outside, `display:
inline-flex` + 44px target inside; `.app-nav` becomes the dropdown:
absolute below the header, `flex-direction: column`, surface background
+ bottom border/shadow, full-width row links; closed state
(`visibility: hidden; opacity: 0; transform: translateY(-8px);
pointer-events: none`) → `.is-open` (`visible; opacity: 1; transform:
none`), `transition: opacity/transform 180ms ease`; a
`prefers-reduced-motion` override kills the transition. The old
nav-pill squeeze rules (`.nav-link` 0.72rem, `.app-nav` gap 0.05rem)
are superseded for the menu rows.
- **JS** (`frontend/assets/header.js`, module-import binding like
sign-out): null-safe `#nav-toggle` / `#app-nav` — click toggles
`.is-open` + `aria-expanded`; delegated click on nav links closes it;
`document` keydown `Esc` closes while open; a
`matchMedia("(max-width: 640px)")` change listener closes on
desktop. No other header.js behavior touched.
- **Non-goals:** no change to the 900px tablet rules, the action pills,
the viewer's title bar height, or the no-CDN/A11 constraints.
## Playwright Mapping Rule
**Test Scenario → `tests/e2e/test_mobile_hamburger_nav.py`** (mock
LLM; DB up):
1. `test_mobile_hamburger_visible_and_bar_roomy` — 375×812: `#nav-toggle`
visible with `aria-expanded="false"`; the inline nav links are not
visible in the bar (menu closed); the header has no horizontal
overflow.
2. `test_anonymous_menu_contents` — anonymous: open the menu → exactly
"Chat" is visible; open/close flips `aria-expanded`.
3. `test_admin_menu_contents` — login: open the menu → Chat / Sources /
Git sources / Tuning all visible (auth contract inside the menu).
4. `test_link_click_navigates_and_closes` — open, click "Sources"
(admin): navigates to `/sources.html`, and the menu on the arrival
page is closed.
5. `test_esc_and_backdrop_close` — open, `Esc` closes (aria-expanded
false); open again, click outside the panel closes.
6. `test_animation_and_reduced_motion` — with motion allowed, the menu
has a transition (computed `transition-duration` ≈180ms on the
opacity/transform pair); with `reducedMotion: "reduce"` emulated,
the transition is none/0s and the menu still opens/closes.
7. `test_desktop_unchanged` (regression) — 1280×800: no hamburger,
inline nav pills exactly as before (the phase-34/35 bar contract,
`test_nav_consistency` / `test_header_consistency` pass in the
regression pass).