Fix mobile navigation behaviour #112

Closed
opened 2026-08-19 02:20:57 +00:00 by Grandiras · 2 comments
Owner

Flyouts from the bottom are nice, but if the button they get triggered by is way up at the top, it gets hard to navigate.

Flyouts from the bottom are nice, but if the button they get triggered by is way up at the top, it gets hard to navigate.
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/302); the reply will link the full session transcript. <!-- claude-run:comment --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-112/run-302.md) — updating every ~5 minutes while the task runs.
Collaborator

Everything is committed, pushed, and cleaned up.

On a phone the nav is now a bar on the bottom edge, so the two controls that open bottom sheets sit where their sheets are.

What changed (app.css, phone block only — everything is inside @media (max-width: 599px) except one inert token and one keyframe):

  • .board-header is position: fixed to the bottom edge below 600px. One bar, not a header plus a tab strip — nothing is named twice and the same 56px is spent once.
  • --phone-nav-h (56px + env(safe-area-inset-bottom), because the bar is what sits in that inset) is the single number everything that has to stop where the bar starts subtracts: the board, the calendar page, the column's max height, the pagination pill, the toasts, and both sheets. body:has(.board-header) pads by it for the pages that just scroll — :has() because sign-in / /signup / /request render no header and 56px of dead space under a centred card is a scrollbar for nothing.
  • The sheets stop at the bar instead of covering it, so the button that opened one is never hidden and a second tap on the same spot is the way back out. That is the ergonomic point: opening from one corner and dismissing by reaching back across the device was two journeys for one decision.
  • The account menu therefore rises 10px rather than sliding up from under the bar: it lives inside the header's stacking context, and nothing inside a stacking context can paint below that context's own background, so a translateY(100%) start would be drawn straight over the bar. The switcher shares the new sheetRise; the card dialog keeps slideUpMobile because it covers everything.
  • The click-catcher ends at the bar for the mirror-image reason — at z-index: -1 it paints above the header's background but below its children, so a full-height dim would tint the bar and leave its buttons crisp on top of the tint.
  • Both sheets keep the top edge they had (the bar's height comes off their old ceiling rather than being added to it), so the strip of dimmed board above a sheet — the only place left to click to dismiss it — is the strip it always was. Both chevrons are rotated: these menus open upward now.

Before and after, 390×780: the trigger moves from the top of the screen to a bar at the bottom, and the sheet now rests on that bar instead of covering it

Also in the branch: a Changelog.Unreleased bullet in both locales; docs/ui.md and docs/boards-and-permissions.md rewritten for the move (including why each of the four consequences above is the way it is); new assertions in mobile.spec.js (bar on the bottom edge, board ending exactly where it starts, sheet seated on the bar, toggle closing what it opened) and boards.spec.js (same for the switcher's sheet).

Verified against a real stack in this container (installed .NET 10, reached the dind daemon, built the image, ran Playwright against :8090):

  • dotnet build + dotnet test — 312 passed, 0 failed.
  • mobile.spec.js on a freshly recreated stack — 6 passed, 1 failed. The failure is the card dialog fills the phone…, which waits for .card .tag-chip-static and creates neither the card nor the tag; a wiped stack has no tagged card. Filed separately.
  • boards.spec.js — the phone test's own assertions all pass; it and four others fail in deleteBoard, where .btn-danger on /admin/boards does nothing when clicked straight after the navigation. That is the exact prerender race docs/testing.md documents as reproducing on an unmodified tree in a job container, it happens at the 1280px default viewport, and every rule I added is scoped under max-width: 599px. I did not touch it.
  • Screenshots above are real renders of the running app; the "before" pane is the same live app with HEAD's app.css served over the wire.

Branch: claude/issue-112.


⏱ 23m 58s · 238 turns · tokens: 252.1k in / 87.6k out (+43221.8k cache-read)
📜 Full session transcript
→ Opened PR: #124
→ Filed follow-up issue #125: mobile.spec.js's card-dialog test needs a tagged card it never creates

Everything is committed, pushed, and cleaned up. On a phone the nav is now a bar on the **bottom** edge, so the two controls that open bottom sheets sit where their sheets are. **What changed** (`app.css`, phone block only — everything is inside `@media (max-width: 599px)` except one inert token and one keyframe): - `.board-header` is `position: fixed` to the bottom edge below 600px. One bar, not a header plus a tab strip — nothing is named twice and the same 56px is spent once. - `--phone-nav-h` (56px + `env(safe-area-inset-bottom)`, because the bar is what sits in that inset) is the single number everything that has to stop where the bar starts subtracts: the board, the calendar page, the column's max height, the pagination pill, the toasts, and both sheets. `body:has(.board-header)` pads by it for the pages that just scroll — `:has()` because sign-in / `/signup` / `/request` render no header and 56px of dead space under a centred card is a scrollbar for nothing. - **The sheets stop *at* the bar instead of covering it**, so the button that opened one is never hidden and a second tap on the same spot is the way back out. That is the ergonomic point: opening from one corner and dismissing by reaching back across the device was two journeys for one decision. - The account menu therefore rises 10px rather than sliding up from under the bar: it lives inside the header's stacking context, and nothing inside a stacking context can paint below that context's own background, so a `translateY(100%)` start would be drawn straight *over* the bar. The switcher shares the new `sheetRise`; the card dialog keeps `slideUpMobile` because it covers everything. - The click-catcher ends at the bar for the mirror-image reason — at `z-index: -1` it paints above the header's background but below its children, so a full-height dim would tint the bar and leave its buttons crisp on top of the tint. - Both sheets keep the top edge they had (the bar's height comes off their old ceiling rather than being added to it), so the strip of dimmed board above a sheet — the only place left to click to dismiss it — is the strip it always was. Both chevrons are rotated: these menus open upward now. ![Before and after, 390×780: the trigger moves from the top of the screen to a bar at the bottom, and the sheet now rests on that bar instead of covering it](https://git.grandiras.net/attachments/e1add718-f827-4ca6-b55b-d2eeb3cb566d) **Also in the branch:** a `Changelog.Unreleased` bullet in both locales; `docs/ui.md` and `docs/boards-and-permissions.md` rewritten for the move (including why each of the four consequences above is the way it is); new assertions in `mobile.spec.js` (bar on the bottom edge, board ending exactly where it starts, sheet seated on the bar, toggle closing what it opened) and `boards.spec.js` (same for the switcher's sheet). **Verified** against a real stack in this container (installed .NET 10, reached the dind daemon, built the image, ran Playwright against `:8090`): - `dotnet build` + `dotnet test` — 312 passed, 0 failed. - `mobile.spec.js` on a freshly recreated stack — **6 passed, 1 failed**. The failure is *the card dialog fills the phone…*, which waits for `.card .tag-chip-static` and creates neither the card nor the tag; a wiped stack has no tagged card. Filed separately. - `boards.spec.js` — the phone test's own assertions all pass; it and four others fail in `deleteBoard`, where `.btn-danger` on `/admin/boards` does nothing when clicked straight after the navigation. That is the exact prerender race `docs/testing.md` documents as reproducing on an unmodified tree in a job container, it happens at the 1280px default viewport, and every rule I added is scoped under `max-width: 599px`. I did not touch it. - Screenshots above are real renders of the running app; the "before" pane is the same live app with `HEAD`'s `app.css` served over the wire. Branch: `claude/issue-112`. --- ⏱ 23m 58s · 238 turns · tokens: 252.1k in / 87.6k out (+43221.8k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-112/run-302.md) → Opened PR: https://git.grandiras.net/Grandiras/Ponente/pulls/124 → Filed follow-up issue [#125](https://git.grandiras.net/Grandiras/Ponente/issues/125): mobile.spec.js's card-dialog test needs a tagged card it never creates
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente#112
No description provided.