From 8357929349542ecc89bd3e19c1da035aec8709a9 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 29 May 2026 18:14:35 -0700 Subject: [PATCH] Derive mobile driver overlay collapse state (#3170) --- .../terminal-pane/MobileDriverOverlay.tsx | 25 +++++++------ .../mobile-driver-overlay-collapse.test.ts | 35 +++++++++++++++++++ .../mobile-driver-overlay-collapse.ts | 25 +++++++++++++ 3 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 src/renderer/src/components/terminal-pane/mobile-driver-overlay-collapse.test.ts create mode 100644 src/renderer/src/components/terminal-pane/mobile-driver-overlay-collapse.ts diff --git a/src/renderer/src/components/terminal-pane/MobileDriverOverlay.tsx b/src/renderer/src/components/terminal-pane/MobileDriverOverlay.tsx index 0f388115b53..1ae7be990ee 100644 --- a/src/renderer/src/components/terminal-pane/MobileDriverOverlay.tsx +++ b/src/renderer/src/components/terminal-pane/MobileDriverOverlay.tsx @@ -3,6 +3,10 @@ import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' import type { DriverState } from '@/lib/pane-manager/mobile-driver-state' import { shouldFocusMobileDriverAction } from './mobile-driver-overlay-focus' +import { + createMobileDriverOverlayCollapseState, + getMobileDriverOverlayCollapseState +} from './mobile-driver-overlay-collapse' type Props = { driver: DriverState @@ -25,7 +29,9 @@ export function MobileDriverOverlay({ const isHeldAtPhoneFit = !isMobileDriving && hasFitOverride const driverClientId = driver.kind === 'mobile' ? driver.clientId : null - const [collapsed, setCollapsed] = useState(false) + const [collapseState, setCollapseState] = useState(() => + createMobileDriverOverlayCollapseState(driverClientId) + ) const [actionPending, setActionPending] = useState(false) const mountedRef = useRef(true) @@ -36,13 +42,12 @@ export function MobileDriverOverlay({ [] ) - // Re-expand on driver flip so a new mobile actor is loud, not silent. - useEffect(() => { - if (!isMobileDriving) { - return - } - setCollapsed(false) - }, [isMobileDriving, driverClientId]) + const currentCollapseState = getMobileDriverOverlayCollapseState(collapseState, driverClientId) + // Why: a new mobile actor must be loud even if the prior driver was collapsed. + if (currentCollapseState !== collapseState) { + setCollapseState(currentCollapseState) + } + const collapsed = currentCollapseState.collapsed if (!isMobileDriving && !isHeldAtPhoneFit) { return null @@ -82,7 +87,7 @@ export function MobileDriverOverlay({ setCollapsed(false)} + onExpand={() => setCollapseState(createMobileDriverOverlayCollapseState(driverClientId))} rootClassName={rootClassName} /> ) @@ -96,7 +101,7 @@ export function MobileDriverOverlay({ actionLabel="Take back" actionPending={actionPending} onAction={handleAction} - onCollapse={() => setCollapsed(true)} + onCollapse={() => setCollapseState({ driverClientId, collapsed: true })} tone="driving" rootClassName={rootClassName} /> diff --git a/src/renderer/src/components/terminal-pane/mobile-driver-overlay-collapse.test.ts b/src/renderer/src/components/terminal-pane/mobile-driver-overlay-collapse.test.ts new file mode 100644 index 00000000000..e2f1e5f79be --- /dev/null +++ b/src/renderer/src/components/terminal-pane/mobile-driver-overlay-collapse.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest' +import { + createMobileDriverOverlayCollapseState, + getMobileDriverOverlayCollapseState +} from './mobile-driver-overlay-collapse' + +describe('mobile-driver-overlay-collapse', () => { + it('preserves collapsed state for the same mobile driver', () => { + const state = { driverClientId: 'phone-1', collapsed: true } + + expect(getMobileDriverOverlayCollapseState(state, 'phone-1')).toBe(state) + }) + + it('re-expands when a new mobile driver takes over', () => { + expect( + getMobileDriverOverlayCollapseState({ driverClientId: 'phone-1', collapsed: true }, 'phone-2') + ).toEqual({ + driverClientId: 'phone-2', + collapsed: false + }) + }) + + it('resets after held-fit mode before the same phone drives again', () => { + const heldState = getMobileDriverOverlayCollapseState( + { driverClientId: 'phone-1', collapsed: true }, + null + ) + + expect(heldState).toEqual(createMobileDriverOverlayCollapseState(null)) + expect(getMobileDriverOverlayCollapseState(heldState, 'phone-1')).toEqual({ + driverClientId: 'phone-1', + collapsed: false + }) + }) +}) diff --git a/src/renderer/src/components/terminal-pane/mobile-driver-overlay-collapse.ts b/src/renderer/src/components/terminal-pane/mobile-driver-overlay-collapse.ts new file mode 100644 index 00000000000..e464a5d8d42 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/mobile-driver-overlay-collapse.ts @@ -0,0 +1,25 @@ +export type MobileDriverOverlayCollapseState = { + driverClientId: string | null + collapsed: boolean +} + +export function getMobileDriverOverlayCollapseState( + state: MobileDriverOverlayCollapseState, + driverClientId: string | null +): MobileDriverOverlayCollapseState { + return state.driverClientId === driverClientId + ? state + : { + driverClientId, + collapsed: false + } +} + +export function createMobileDriverOverlayCollapseState( + driverClientId: string | null +): MobileDriverOverlayCollapseState { + return { + driverClientId, + collapsed: false + } +}