Skip to content

feat(mobile): rework the sidebar picker, the view switch and the home for phones - #7019

Merged
tlgimenes merged 3 commits into
mainfrom
claude/mobile-org-picker-drawer
Sep 4, 2026
Merged

feat(mobile): rework the sidebar picker, the view switch and the home for phones#7019
tlgimenes merged 3 commits into
mainfrom
claude/mobile-org-picker-drawer

Conversation

@tlgimenes

@tlgimenes tlgimenes commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Reported from a phone: the org/project menu would not scroll. Pulling on that turned up three more things the mobile shell got wrong, all fixed here.

1. The picker could not be scrolled by touch

On mobile the sidebar is a Radix Sheet — a modal dialog — whose scroll lock (react-remove-scroll) calls preventDefault() on every wheel and touchmove landing outside the dialog's own DOM subtree. The picker's popover is portalled to document.body, so it was outside: the list clipped at its max height and would not move under a finger. The repo already knew this failure mode for wheel and patched it by hand in page-template-select.tsx.

Also, the height cap above it was dead code — Command declared max-h-[min(560px,70dvh)], but CommandList's shared max-h-[300px] default is smaller, so the picker was always ~373px tall whatever that number said. The list now takes the Command's budget and clamps to --radix-popover-content-available-height, so a picker opened low on a short screen ends where the screen does.

2. Two selectors over one entity

The sheet header carried an agent switcher beside the org/project picker. Agents and projects are both virtual MCPs, so those were the same things listed twice under two names — which is what made the header read as two competing scopes.

The agent switcher goes; the picker is the single selector there. Nothing else in the app opened the agent browser, so this orphaned agents-section.tsx, the agent crumb in shell-breadcrumb.tsx, and read-cached-task-branch.ts — ~1000 lines knip confirms nothing imports. They go here, with the i18n keys only they read. Desktop is unaffected: it never had an agent crumb.

3. A popover hung off a control under your thumb

The picker now opens as a bottom drawer on mobile and stays a popover on the desktop it was drawn for — the drawer presentation borrowed from the agent picker it replaces. That also settles (1) on mobile without a modal popover: a drawer is its own modal layer, so the list scrolls. Picking now closes the sheet too; before, whatever you chose stayed hidden behind it.

4. Chat was a dead end

On a route with no tabs of its own (the org home — the first one people meet) the view select listed ["Chat"] alone. "Main view" was a label it fell back to, never an option, so tapping Chat left no way back but the browser's back button. Confirmed live before the fix: the open dropdown read ["Chat", "Chat"] (hidden sentinel + the one option).

buildMobileViewOptions is now a pure, unit-tested function that synthesizes a main-surface row exactly when no tab already leads back — where the route declares tabs, opening one already opens the main panel, so a second entry would only duplicate the first.

And two surfaces render a toggle button, not a dropdown: one tap and a label beats a menu that opens to offer a single alternative. Three or more keep the select, because past two there is no single "the other one". The button names where it takes you (Chat / Main view, aria-label="Switch to …").

5. The connect pill is desktop-only now

It offers to install a CLI on the machine you are sitting at, which is not the phone you are holding — and it took the first screenful of the mobile home to propose it. hidden md:inline-flex, so the column's gap closes with it and the greeting is what you land on.

Testing

Driven on the running app with device emulation.

Touch-scroll, probed with a real dispatched touch sequence over the list:

touchmove prevented?
popover, before true → cannot scroll
popover + modal false → scrolls
drawer (what shipped) false → scrolls
drawer, list at its end true → no scroll chaining to the page behind

At 390×844 with touch: the toggle round trips main → chat → main one tap each, label and aria-label flipping with it; the drawer opens from the bottom (data-vaul-drawer-direction="bottom") with search and both groups; sheet header controls are now exactly ["Organization and project: …", "Close sidebar", …nav]; the connect pill computes display: none.

At 1280×800: popover (no drawer), Command computed max-height: 560px, body { pointer-events: auto } confirming it is non-modal — no scrollbar-gap shift, and the org shell is h-dvh overflow-hidden so the body never scrolls anyway.

bun run fmt, bun run lint (0 errors), bun run check across every workspace, knip (clean), and 317 unit tests in the touched trees pass.

Note for whoever runs the suite: bun test apps/web/src as one process reports 111 failures — identical on main at this commit, none introduced here. They are DOM-global collisions between suites batched into one bun process (SSR / window.opener / localStorage), worth a separate look.

Follow-ups (not here)

  • The account popover (account-popover.tsx) and the inbox popover (sidebar/footer/inbox.tsx) sit in the same mobile sheet and carry the same latent scroll-lock bug — the inbox one has a real scrolling list.
  • The agent browser's extras died with it: server-side agent search, "Import from GitHub", and the "See all agents" footer. The picker has its own search and "New project" but not those; if they matter they belong on the picker rather than resurrected.
  • With the on-screen keyboard open no CSS unit shrinks (dvh tracks the layout viewport, not the visual one), so a tall surface can still extend under the keyboard. A real fix is a separate visualViewport job.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AVd1o11cJLXR7HPSTyuyBC

@github-actions github-actions Bot added the claude PR authored by a coding agent label Sep 4, 2026
On mobile the sidebar is a Radix Sheet — a modal dialog — and its scroll
lock (react-remove-scroll) calls preventDefault() on every wheel and
touchmove that lands outside the dialog's own subtree. The picker's
popover is portalled to the body, so it is outside: the list clipped at
its max height and would not move under a finger. The repo already knew
this failure mode for wheel (page-template-select.tsx patches it by
hand). Making the popover modal gives it its own lock — only the
innermost lock acts, and that one counts the list as scrollable — and it
restores the menu semantics this control inherited from the dropdown it
replaced.

While in there: the Command's max-h-[min(560px,70dvh)] was dead, because
CommandList's shared 300px default is smaller, so the picker was always
~373px tall whatever that number said. The list now takes the Command's
budget, and the budget clamps to --radix-popover-content-available-height
so a picker opened low on a short screen ends where the screen does.

Verified against the real Sheet + PopoverContent + Command components in
Chrome with a dispatched touch sequence over the list: touchmove is
prevented before the change, allowed after, and still prevented once the
list reaches its end, so nothing chains to the page behind. Layout at an
806px viewport: Command 560, input 40, strip 33, list 487 and scrollable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AVd1o11cJLXR7HPSTyuyBC
@tlgimenes
tlgimenes force-pushed the claude/compressed-painting-koala branch from 8ada66b to d726fb6 Compare September 4, 2026 19:36
tlgimenes and others added 2 commits September 4, 2026 16:36
…e for two views

Three things the mobile shell got wrong, reported from a phone.

**Two selectors over one entity.** The sidebar sheet header carried an agent
switcher beside the org/project picker. Agents and projects are both virtual
MCPs, so those were the same things listed twice under two names. The agent
switcher goes; the picker stays and is now the single selector there. That
orphaned its whole browser — agents-section.tsx, the agent crumb, and
read-cached-task-branch, ~1000 lines nothing else imported — so they go with it,
along with the i18n keys only they read.

**A popover hung off a control under your thumb.** The picker now opens as a
bottom drawer on mobile and stays a popover on the desktop it was drawn for.
That also settles the scroll bug from the branch below this one without the
`modal` popover: portalled outside the sheet, the popover sat outside that
dialog's scroll lock, which cancels every touchmove over it. A drawer is its own
modal layer, so the list scrolls. Picking now closes the sheet too, or what you
just chose stays hidden behind it.

**Chat was a dead end.** On a route with no tabs of its own the view select
listed `[Chat]` alone — "Main view" was a label it fell back to, never an option
— so tapping Chat left no way back but the browser's back button. The options
builder is now a pure function that synthesizes a main-surface row exactly when
no tab already leads back, and it is unit-tested. Two surfaces render a toggle
instead of a dropdown, per the ask: one tap and a label beats a menu that opens
to offer one alternative. Three or more keep the select.

Verified on the running app at 390x844 with touch emulation: the toggle round
trips main -> chat -> main one tap each; the drawer opens from the bottom with
search and both groups; a touchmove over its list is no longer cancelled
(defaultPrevented false with the list scrollable). Desktop unchanged — popover,
no drawer, Command capped at the 560px it always meant to be, body
pointer-events back to `auto` now that it is non-modal again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AVd1o11cJLXR7HPSTyuyBC
It offers to install a CLI on the machine you are sitting at, which is not the
phone you are holding — and it took the first screenful of the mobile home to
propose it. `hidden md:inline-flex`, so the greeting is what you land on and the
column's gap closes with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AVd1o11cJLXR7HPSTyuyBC
@tlgimenes
tlgimenes force-pushed the claude/mobile-org-picker-drawer branch from db0c3d0 to 73b7164 Compare September 4, 2026 19:38
@tlgimenes
tlgimenes changed the base branch from claude/compressed-painting-koala to main September 4, 2026 19:40
@tlgimenes tlgimenes changed the title feat(mobile): one selector in the sheet, a drawer to open it, a toggle for two views feat(mobile): rework the sidebar picker, the view switch and the home for phones Sep 4, 2026
@tlgimenes
tlgimenes enabled auto-merge (squash) September 4, 2026 19:41
@tlgimenes tlgimenes closed this Sep 4, 2026
auto-merge was automatically disabled September 4, 2026 19:42

Pull request was closed

@tlgimenes tlgimenes reopened this Sep 4, 2026
@tlgimenes
tlgimenes enabled auto-merge (squash) September 4, 2026 19:43
@tlgimenes
tlgimenes merged commit f115405 into main Sep 4, 2026
37 checks passed
@tlgimenes
tlgimenes deleted the claude/mobile-org-picker-drawer branch September 4, 2026 19:52
decocms Bot pushed a commit that referenced this pull request Sep 4, 2026
PR: #7019 feat(mobile): rework the sidebar picker, the view switch and the home for phones
Bump type: minor

- decocms (apps/api/package.json): 4.331.2 -> 4.332.0
- @decocms/native (apps/native/package.json): 4.331.2 -> 4.332.0

Deploy-Scope: web
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude PR authored by a coding agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant