From 36e039c656ad0a2320173bdf923c00481ab89d7e Mon Sep 17 00:00:00 2001 From: pythonlearner1025 Date: Sat, 5 Sep 2026 22:55:31 -0700 Subject: [PATCH] fix(mobile): the session rail is the workspace's screen, not a drawer Below 900px the rail slid in at min(88vw, 348px) over the chat, behind a scrim. It now fills the width, and a workspace opens on it. Picking a session hides it, so the pick reads as a page change. Four more phone defects go with it: - Create workspace opened at z-index 100 under the rail's z-index 400. Its handler now closes the rail first, like its five siblings. - The create-workspace footer was pinned and ate 70px of every screen. Its buttons also hugged the right edge, because the base rule's justify-content survived the mobile override. The dialog now scrolls as one page, the header sticks, and the buttons end the form. - The member row is an eight-column grid that dropped to three columns at 620px, so five children auto-placed into 28px columns and overlapped. The four machine controls now share one wrapper: display: contents on desktop, a wrapped band on a phone. - The scrim can no longer cover anything, so it is deleted rather than hidden. Desktop does not move. Every edit sits inside an existing mobile media query or behind mobileWebApp, and test/mobile-only-styles.test.ts proves it by parsing the stylesheets. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_019ZvBaFqU4mgX75HAbiYJYV --- packages/webapp/src/CloudApp.tsx | 11 +- .../webapp/src/WorkspaceMembersEditor.tsx | 80 ++++++------- .../webapp/src/create-workspace-dialog.css | 8 ++ packages/webapp/src/shell/ShellNav.tsx | 81 ++++++------- packages/webapp/src/shell/mobile-rail.ts | 7 ++ packages/webapp/src/strip-rail.css | 14 +-- .../webapp/src/workspace-details-dialog.css | 16 ++- .../test/WorkspaceDetailsDialog.test.tsx | 6 + .../webapp/test/mobile-only-styles.test.ts | 110 ++++++++++++++++++ .../webapp/test/mobile-rail-route.test.ts | 28 +++++ .../webapp/test/shell-mobile-drawer.test.tsx | 21 ++-- packages/webapp/test/shell-smoke.test.tsx | 53 +++++++++ 12 files changed, 323 insertions(+), 112 deletions(-) create mode 100644 packages/webapp/src/shell/mobile-rail.ts create mode 100644 packages/webapp/test/mobile-only-styles.test.ts create mode 100644 packages/webapp/test/mobile-rail-route.test.ts diff --git a/packages/webapp/src/CloudApp.tsx b/packages/webapp/src/CloudApp.tsx index 64742a45..cb328114 100644 --- a/packages/webapp/src/CloudApp.tsx +++ b/packages/webapp/src/CloudApp.tsx @@ -35,6 +35,7 @@ import { AccessApprovalDialog } from './AccessApprovalDialog'; import { useAccessProposals } from './use-access-proposals'; import type { ConnectionsFocus, WorkspaceDetailsTab } from './WorkspaceDetailsDialog'; import { ShellNav } from './shell/ShellNav'; +import { routeShowsMobileRail } from './shell/mobile-rail'; import { isSecondaryRoute, SecondaryRoutes } from './shell/SecondaryRoutes'; import { NewTabControl } from './shell/NewTabControl'; import { WorkPanes } from './shell/WorkPanes'; @@ -283,9 +284,10 @@ function CloudAppContent({ client, resolver }: CloudAppProps) { if (!mobileWebApp) setDrawerOpen(false); }, [mobileWebApp]); + const railIsWorkspaceScreen = routeShowsMobileRail(route); useEffect(() => { - setDrawerOpen(false); - }, [route.page, route.workspaceId]); + setDrawerOpen(mobileWebApp && railIsWorkspaceScreen); + }, [mobileWebApp, railIsWorkspaceScreen, route.workspaceId]); useEffect(() => { if (!mobileWebApp) return; @@ -1685,7 +1687,10 @@ function CloudAppContent({ client, resolver }: CloudAppProps) { if (mobileWebApp) setDrawerOpen(false); setDetails({ workspaceId, tab: 'members', focusAddMember: true }); }} - onCreateWorkspace={() => setShowCreateWorkspace(true)} + onCreateWorkspace={() => { + if (mobileWebApp) setDrawerOpen(false); + setShowCreateWorkspace(true); + }} onOpenSettings={() => navigateToSettings('profile')} onSelectSession={selectTtydSession} onCloseSession={closeTtydSession} diff --git a/packages/webapp/src/WorkspaceMembersEditor.tsx b/packages/webapp/src/WorkspaceMembersEditor.tsx index 816b8d25..706650e8 100644 --- a/packages/webapp/src/WorkspaceMembersEditor.tsx +++ b/packages/webapp/src/WorkspaceMembersEditor.tsx @@ -248,47 +248,49 @@ function MemberRow({ }} /> )} - {showMachine && } - {showMachine && ( - - )} - {showVolume && (machine === null ? ( - - ) : ( - // The disk exists, so the row reports it instead of offering a choice - // this route cannot make: how full it is, or that there is none. - - ))} - {showMachine && actions.length > 0 && ( - ({ value: action, label: MACHINE_ACTION_LABELS[action] }))} - onChange={(next) => { - // SAFETY: the options are exactly the MachineAction values above. - onMachineAction?.(next as MachineAction); - }} - /> - )} + )} + {showVolume && (machine === null ? ( + + ) : ( + // The disk exists, so the row reports it instead of offering a choice + // this route cannot make: how full it is, or that there is none. + + ))} + {showMachine && actions.length > 0 && ( + ({ value: action, label: MACHINE_ACTION_LABELS[action] }))} + onChange={(next) => { + // SAFETY: the options are exactly the MachineAction values above. + onMachineAction?.(next as MachineAction); + }} + /> + )} + {!readOnly && !pinned && (