The #21 user-account features added several new UI surfaces, and on phones the header bar in particular is now crowded. Time to polish the mobile UX for the whole authenticated layer.
## The header bar (worst offender)
`Layout.astro`'s nav now holds, in one horizontal row: the **AI Lab Tracker** brand + **Labs**, **Timeline**, **What's New**, the **search** icon, **Collections** (shown when signed in), and the **Sign in / @user** button. At 400px the CSS only drops the gap to 0.5rem and the font to 0.8rem — that's 5–6 tap targets plus a wordmark competing for ~360px. Signed-in users (who now have Collections + @username) are the most cramped, and the whole nav is `position: sticky` on phones so it eats vertical space too.
## The new per-item controls
Each also needs a mobile pass (they were built desktop-first as inline dropdowns/panels):
- **SavedViews** (`★ Saved views` dropdown) and **★ My tags** (multiselect dropdown) on the home + timeline tables — absolutely-positioned menus that can overflow a narrow viewport.
- **TagEditor** (chips + text input) on output/lab pages — chips + a 200px-min input in a flex row.
- **CollectionControl** (`+ Collections` dropdown) — absolute menu.
- **NotesPanel** (textarea + note list) — mostly fine but untested on mobile.
- **/collections** page — 2-col grid collapses to 1-col at 600px (already handled), but the create form + item rows want a check.
- **/account** — the tags overview `at-row` is a flex that may wrap awkwardly.
## Constraints (existing conventions, issue #17)
- **600px phone breakpoint**; tables stay tables (CSS column-hide only, never wrap a sticky table in `overflow-x`); `.kbd-only` for shortcut chrome; 16px inputs under `pointer: coarse`; **`npm run test:mobile`** (structural no-overflow, 7 widths + landscape) must stay green.
## Plan
Deep research on best-practice mobile UX for these surfaces → detailed plan → adversarial review (measured with the dev server / test:mobile, per the #17 precedent where critics disproved the plan's arithmetic twice) → implement. Will post the plan + critique here before/at implementation.
## Plan + adversarial review (both measured with Playwright, per the #17 discipline)
**Measured facts:** the nav is the *sole* whole-page horizontal-scroll source. Overflow (`.nav-inner` scrollWidth−clientWidth): logged-out 24–94px, signed-in (Collections + @user) 169–239px, across 320–414px. Two bugs I shipped: (a) new components `return` on a 401 **without draining the response body** → leaked connections + `test:mobile` can't even start (`networkidle` never settles); (b) new inputs miss the 16px-under-`pointer:coarse` rule (iOS focus-zoom).
**Plan:** P0 — drain the leaked 401 bodies (`res.body?.cancel()`) + add 16px inputs. P1 — collapse the nav on ≤600px into `[brand] … [☰ menu][🔍 search]`, the menu opening a panel with the 3 page links + auth (Sign in / @user·Collections·Sign out). P2 — 44px tap targets, backdrop/Escape/outside-click dismissal for the on-table dropdowns, confirm-on-delete for notes.
**Adversarial review re-measured the plan and corrected three things:**
1. **Collapsing the links alone gives 0px nav overflow at 320–414 in both auth states — brand-shortening is unnecessary** (keep "AI Lab Tracker").
2. **Bar order must be `[☰][🔍]`, not `[🔍][☰]`.** The SearchDropdown is right-anchored; menu-rightmost clips it −26px at every phone width. (My draft plan would have shipped that regression.)
3. **Portal the panel + backdrop to `<body>`.** The sticky nav (`z-index:100`) is a stacking context; a nav-nested panel is painted over by the FilterBar sheet (z-150). Render at root, z-index >150, and mutually close with the search dropdown / filter sheet.
Also: body-drain measured at 511ms-settle vs >8s-timeout; only 4 endpoints leak (not `/api/me`); **P0 + P1 must ship together** (drain alone turns the suite from timeout to red, not green). Disproven worries: 41-char tags don't overflow the dropdown menus (they wrap); the search input is already 16px; DOM-injection is a sound way to add a signed-in nav-overflow assertion the current suite lacks.
**Verification:** `test:mobile` green at all 7 widths + landscape, plus a new injected-signed-in-nav no-overflow assertion; re-measure the collapse at 320–414 in both auth states.
Implementing now.
**Implemented + measured (commit `e4833cf`, local, not pushed).** `npm run test:mobile` → **64/64**, including 4 new signed-in-nav assertions the suite previously lacked.
### P0 — bugs that also blocked the test suite
- **Drained the leaked 401 response bodies** (`res.body?.cancel()`) in the 5 feature components + account/collections. Undrained bodies kept connections pending → `test:mobile` couldn't even start (networkidle timeout). Now settles.
- **Site-wide 16px input rule under `(pointer: coarse)`** — stops iOS focus-zoom on all the new fields.
### P1 — the nav (was the *sole* whole-page h-scroll source; 169–239px signed-in)
- On ≤600px the bar collapses to **`[brand] … [☰ menu][🔍 search]`** (search kept rightmost so the right-anchored SearchDropdown can't clip — the adversarial catch). Page links + Collections + account move into a menu **panel + backdrop rendered at `<body>` level** so they escape the sticky nav's stacking context and paint above the FilterBar sheet (the other adversarial catch). Escape / backdrop / outside-click dismiss + scroll-lock. Removed the non-monotonic sub-400px relaxations.
- **Measured 0px nav overflow at 360/375/390/412 in BOTH logged-out and signed-in states** (down from 169–239px signed-in). The full "AI Lab Tracker" wordmark stays — collapsing the links alone sufficed, so no brand-shortening (also per the measured review).
### P2 — touch polish
- 44px tap targets (coarse pointer) on chip-remove, note edit/delete, menu/checkbox rows; **Escape + outside-click dismissal** for the three on-table dropdowns (SavedViews, My tags, Collections); **confirm-on-delete** for notes.
### Test coverage
- Added a **signed-in nav no-overflow assertion** (inject Collections + a long `@username`, assert `.nav-inner` fits) — the suite ran logged-out only and would have missed the signed-in blow-out. 64/64 green.
Out of scope / noted: at **320px** the home `.lab-table` (322px) overflows 18px — pre-existing, unrelated to these features, and below the suite's supported minimum (360). Flagging, not fixing here. 🤖
The #53 UX cleanup (commit `3eca82c`, now deployed) likely covers this issue's remaining scope — and notably discovered that the per-item controls' mobile CSS (44px targets etc.) from the #48 pass never actually applied to runtime-created DOM until the `is:global` fix there. The nav collapse from the earlier #48 work plus #53's control polish leaves, I believe, nothing actionable here except #50 (tracked separately). Leaving the close call to review — details in #53's implementation comment.
Closing per review — covered by the #53 cleanup (deployed in `3eca82c`): nav collapse landed in the earlier #48 pass, the per-item controls got the interaction/consistency polish in #53, and the is:global fix made the mobile CSS (44px targets, 16px inputs) actually apply to the runtime-created DOM. Remaining bottom-sheet work is tracked in #50.