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

5.6 KiB
Raw Blame History

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).