From e8be7ef55b13c6b86e8c6ab3dcfb1fe6f504b9cc Mon Sep 17 00:00:00 2001 From: Tom Rochette Date: Sat, 11 Jul 2026 19:56:29 -0400 Subject: [PATCH] fix(mobile): eliminate >10s sidebar open delay by always mounting SessionSidebar (#1695) (#1738) * fix(mobile): always mount SessionSidebar to eliminate >10s drawer open delay (#1695) On mobile (Android PWA), SessionSidebar was conditionally mounted via {mobileLeftDrawerVisible && ...}, causing the component to unmount on drawer close and remount on every open. Each remount fired a full data-loading cascade: paginated sessions fetch (PAGE_SIZE=500 with retry), worktree discovery, repo status, PR status, 10+ useMemo recomputations, and localStorage reads, manifesting as a >10s delay before the drawer became interactive. Desktop already avoided this by keeping SessionSidebar always mounted inside with a CSS visibility toggle. Fix: remove the mobileLeftDrawerVisible conditional wrapper so SessionSidebar stays mounted on mobile too, matching desktop behavior. Visibility remains controlled by the leftDrawerX transform (off-screen when closed). Added pointer-events-none when hidden as a defensive guard. Added a regression test that fails if the conditional mount pattern is reintroduced around the mobile SessionSidebar. * fix(mobile): hide closed drawer to avoid rotation offset leak --------- Co-authored-by: Bohdan Triapitsyn --- .../ui/src/components/layout/MainLayout.tsx | 31 ++++++++++++---- .../mainLayoutMobileSidebarMount.test.ts | 35 +++++++++++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 packages/ui/src/components/layout/__tests__/mainLayoutMobileSidebarMount.test.ts diff --git a/packages/ui/src/components/layout/MainLayout.tsx b/packages/ui/src/components/layout/MainLayout.tsx index 04b8bf23..891e16fd 100644 --- a/packages/ui/src/components/layout/MainLayout.tsx +++ b/packages/ui/src/components/layout/MainLayout.tsx @@ -457,13 +457,30 @@ export const MainLayout: React.FC = () => { )} - {mobileLeftDrawerVisible && ( - - - - - - )} + {/* Always mount SessionSidebar on mobile to match desktop behavior. + Conditional mount (mobileLeftDrawerVisible && ...) caused a + data-loading cascade on every drawer open: paginated sessions + fetch, worktree discovery, repo status, PR status, and 10+ memo + recomputations. On Android PWA this manifested as a >10s delay + before the drawer became interactive (issue #1695). Visibility is + controlled by the leftDrawerX transform (off-screen when closed). + The invisible class matters when fully hidden: leftDrawerWidth is + not recomputed on resize/rotation, so a closed drawer translated by + the old width could otherwise peek into the viewport; it also keeps + the off-screen sidebar out of the tab order and skips painting it. */} + + + + + {mobileRightDrawerVisible && ( diff --git a/packages/ui/src/components/layout/__tests__/mainLayoutMobileSidebarMount.test.ts b/packages/ui/src/components/layout/__tests__/mainLayoutMobileSidebarMount.test.ts new file mode 100644 index 00000000..d3d977ab --- /dev/null +++ b/packages/ui/src/components/layout/__tests__/mainLayoutMobileSidebarMount.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from 'bun:test'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const mainLayoutSource = readFileSync( + join(__dirname, '..', 'MainLayout.tsx'), + 'utf-8', +); + +describe('MainLayout mobile SessionSidebar mount (issue #1695 regression guard)', () => { + test('mobile SessionSidebar is not conditionally mounted on mobileLeftDrawerVisible', () => { + const mobileSidebarIndex = mainLayoutSource.indexOf(' { + const desktopSidebarIndex = mainLayoutSource.indexOf(''); + expect(desktopSidebarIndex).toBeGreaterThan(-1); + + const windowStart = Math.max(0, desktopSidebarIndex - 300); + const precedingWindow = mainLayoutSource.slice(windowStart, desktopSidebarIndex); + + expect(precedingWindow).toContain('