From e3a9ccb4729fcc228fcb347d4bf03423a47aa8af Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:40:59 -0700 Subject: [PATCH] fix(runtime): cancel pending driver timers on desktop reclaim --- .../mobile-subscribe-integration.test.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/main/runtime/mobile-subscribe-integration.test.ts b/src/main/runtime/mobile-subscribe-integration.test.ts index cee4fcfe79f..a4da469a538 100644 --- a/src/main/runtime/mobile-subscribe-integration.test.ts +++ b/src/main/runtime/mobile-subscribe-integration.test.ts @@ -851,6 +851,65 @@ describe('mobile subscribe integration', () => { expect(ptySizes.get('pty-1')).toEqual({ cols: 150, rows: 40 }) }) + it('reclaim cancels the soft-leave timer before it can clear the desktop driver', async () => { + const { runtime } = createRuntime() + await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 }) + runtime.handleMobileUnsubscribe('pty-1', 'client-a') + + // The last unsubscribe arms the 250ms soft-leave timer while the fit is held. + expect(runtime.getDriver('pty-1')).toEqual({ kind: 'mobile', clientId: 'client-a' }) + expect(await runtime.reclaimTerminalForDesktop('pty-1')).toBe(true) + expect(runtime.getDriver('pty-1')).toEqual({ kind: 'desktop' }) + + await vi.advanceTimersByTimeAsync(250) + expect(runtime.getDriver('pty-1')).toEqual({ kind: 'desktop' }) + }) + + it('reclaim cancels a stale soft-leave timer in the active-subscriber branch', async () => { + const { runtime } = createRuntime() + await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 }) + runtime.handleMobileUnsubscribe('pty-1', 'client-a') + // A different client can subscribe while the first client's grace timer is armed. + await runtime.handleMobileSubscribe('pty-1', 'client-b', { cols: 40, rows: 18 }) + + expect( + (Reflect.get(runtime, 'pendingSoftLeavers') as Map).has('pty-1') + ).toBe(true) + await runtime.reclaimTerminalForDesktop('pty-1') + expect( + (Reflect.get(runtime, 'pendingSoftLeavers') as Map).has('pty-1') + ).toBe(false) + }) + + it('reclaim cancels a stale soft-leave timer in the orphan-driver branch', async () => { + const { runtime } = createRuntime() + await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 }) + runtime.handleMobileUnsubscribe('pty-1', 'client-a') + // Simulate a stale mobile lock whose fit override was already cleared. + const fitOverrides = Reflect.get(runtime, 'terminalFitOverrides') as Map + fitOverrides.delete('pty-1') + + expect(await runtime.reclaimTerminalForDesktop('pty-1')).toBe(true) + expect( + (Reflect.get(runtime, 'pendingSoftLeavers') as Map).has('pty-1') + ).toBe(false) + }) + + it('reclaim cancels pending timers even when no driver lock remains', async () => { + const { runtime } = createRuntime() + await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 }) + runtime.handleMobileUnsubscribe('pty-1', 'client-a') + const currentDriver = Reflect.get(runtime, 'currentDriver') as Map + currentDriver.set('pty-1', { kind: 'idle' }) + const fitOverrides = Reflect.get(runtime, 'terminalFitOverrides') as Map + fitOverrides.delete('pty-1') + + expect(await runtime.reclaimTerminalForDesktop('pty-1')).toBe(false) + expect( + (Reflect.get(runtime, 'pendingSoftLeavers') as Map).has('pty-1') + ).toBe(false) + }) + it('reclaimTerminalForDesktop prefers fresh desktop geometry for a held PTY', async () => { // Why: manual restore must honor fresh desktop geometry measured while the PTY was phone-held, not the stale baseline. settingsState.mobileAutoRestoreFitMs = null