Skip to content

Mobile web UI [6/7]: Keep the nav reachable while the side panel is open - #298

Open
alex-clickhouse wants to merge 1 commit into
alex-clickhouse/mobile-card-widthfrom
alex-clickhouse/mobile-sidepanel
Open

Mobile web UI [6/7]: Keep the nav reachable while the side panel is open#298
alex-clickhouse wants to merge 1 commit into
alex-clickhouse/mobile-card-widthfrom
alex-clickhouse/mobile-sidepanel

Conversation

@alex-clickhouse

Copy link
Copy Markdown
Collaborator

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 md and 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-0 covers 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-0 against ChatPage'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-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 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 build and eslint clean
  • 412×915: panel opens over the chat column, content scrolls inside it, bottom nav visible and navigable throughout
  • 1280×900: unchanged — still a resizable right-hand column beside the transcript, drag handle intact

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

@alex-clickhouse alex-clickhouse changed the title Keep the nav reachable while the side panel is open Mobile web UI [6/7]: Keep the nav reachable while the side panel is open Aug 9, 2026
@alex-clickhouse
alex-clickhouse requested a balanced review from Copilot August 9, 2026 17:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread web/src/components/Layout/AppShell.tsx Outdated
Comment on lines +24 to +27
style={{
paddingTop: 'env(safe-area-inset-top)',
paddingLeft: 'env(safe-area-inset-left)',
paddingRight: 'env(safe-area-inset-right)',

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@alex-clickhouse
alex-clickhouse force-pushed the alex-clickhouse/mobile-sidepanel branch from beeca8e to 40d06b9 Compare August 9, 2026 18:32
@alex-clickhouse
alex-clickhouse marked this pull request as ready for review August 9, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants