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/).
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user