Mobile web UI [6/7]: Keep the nav reachable while the side panel is open - #298
Mobile web UI [6/7]: Keep the nav reachable while the side panel is open#298alex-clickhouse wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Keeps mobile navigation accessible while chat side panels are open and adds safe-area spacing.
Changes:
- Anchors mobile side panels within the chat area.
- Prevents panel content from overflowing onto navigation.
- Adds shell-level top and side safe-area padding.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
web/src/pages/ChatPage.tsx |
Establishes the panel positioning context. |
web/src/components/Chat/SidePanel.tsx |
Constrains the mobile panel above bottom navigation. |
web/src/components/Layout/AppShell.tsx |
Adds mobile safe-area padding. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| style={{ | ||
| paddingTop: 'env(safe-area-inset-top)', | ||
| paddingLeft: 'env(safe-area-inset-left)', | ||
| paddingRight: 'env(safe-area-inset-right)', |
There was a problem hiding this comment.
Fixed. Each fixed surface now pays its own insets through a shared safeAreaInsets() helper, so there is one place to be wrong: SessionSidebar had none at all and now takes top/bottom/left, and Drawer gains the side inset for the edge it is anchored to on top of the top/bottom it already had. The shell keeps its padding for everything actually laid out inside it, with a comment recording why it cannot reach a position: fixed descendant.
The side panel itself is no longer in that category — this PR makes it absolute, so it sits inside the shell padding box and inherits the insets.
The side panel — sub-agents, plans, modified files, workflow tabs — was made a full-screen overlay for phones in the first PR of this series, and that was as far as it got tested. Opening it on a phone shows three problems. `fixed inset-0` covered the bottom nav as well as the chat column, so the close button was the only way out of the panel: Android's back button does not apply either. It is now `absolute inset-0` against ChatPage's root, covering the chat column only, and the nav stays visible and usable underneath. That also stops it being a modal, so it stops claiming to be one: it is a named region rather than a dialog, it no longer traps Tab or takes Escape, and the column it covers is marked `inert` instead. Nothing behind it is tabbable — the transcript and the composer are invisible under it — while the nav outside it stays reachable, which was the point. `inert` also moves focus off the covered composer, so keystrokes stop landing in a box that is no longer on screen. It also dropped the `overflow-hidden` the desktop panel carries. Flex items default to min-height:auto, so the `flex-1 overflow-y-auto` content region grew past the panel and spilled its last rows over the nav bar rather than scrolling inside itself. Third, and not specific to the panel: `viewport-fit=cover` lets the layout reach under the notch and the rounded corners, which is what makes the background continuous, but it also puts *content* there unless something pays the inset back. The shell now applies the top and side insets once, covering every page laid out inside it; the bottom stays with the nav, the element actually against that edge. The shell cannot cover the drawers, though. A `position: fixed` element is laid out against the viewport, not against the shell's padding box, so its insets never reach one — opening the session drawer still put its search field under the status bar. Each fixed surface pays its own, via a shared `safeAreaInsets()` so there is one place to be wrong: the session drawer had none at all, and the shared `Drawer` was missing the side inset for the edge it is anchored to. Refs #271
beeca8e to
40d06b9
Compare
Follow-up on #271, stacked on #297. Fixes the right-hand side panel — sub-agents, plans, modified files, workflow tabs — on phones.
Why this exists
#293 made the panel a full-screen overlay below
mdand I verified the chat page around it, never the panel itself. Opening it on a phone shows three problems, all mine.1. It covered the bottom nav
fixed inset-0covers the whole viewport, nav included, so the ✕ was the only way out — no Escape handler, and Android's back button doesn't apply to a div.Now
absolute inset-0againstChatPage's root, so it covers the chat column only and the nav stays visible and usable underneath. The panel is a focused view, not a trap.2. Its content spilled over the nav
The mobile branch dropped the
overflow-hiddenthe desktop panel carries. Flex items default tomin-height: auto, so theflex-1 overflow-y-autocontent region grew past the panel and painted its last rows over the nav bar instead of scrolling inside itself.This one only appeared after fixing #1 — with the panel covering the whole screen there was nothing underneath for it to spill onto.
3. Safe areas — not specific to the panel
viewport-fit=cover(added in #294) lets the layout reach under the notch and the rounded corners, which is what keeps the background continuous. But it also puts content there unless something pays the inset back, and only the bottom nav and the drawers did. Every page header was sitting under the status bar on a notched device.The shell now applies the top and side insets once, so every page inherits them. The bottom stays with
BottomNav, which is the element actually against that edge.I would rather this were its own PR, but it is the same class of bug the panel exposed and the panel cannot be called fixed while the header above it is under the status bar.
No Escape-to-close
Considered and rejected. On a phone there is no keyboard to press it with; on desktop the panel is a resizable column rather than a modal, so Escape is not expected there either. Adding it would mean threading another case through the documented Esc cascade in
App.tsx(shortcuts modal → sidebar search → stop generation) for no real gain.Verification
npm run buildandeslintcleanThe safe-area behaviour is the one thing I cannot verify here. Chromium's emulation does not synthesise
env(safe-area-inset-*), so that part is reasoned from the spec and needs a real notched device to confirm.