From 76b98391a174bc60bdc201245e5b2da90f801ff0 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Mon, 31 Aug 2026 12:57:46 -0700 Subject: [PATCH] fix(remote): preserve reconnecting runtime state --- .../settings/RuntimeEnvironmentsPane.test.ts | 22 +++++++++++++++++-- .../runtime-environment-host-details.ts | 10 ++++----- .../runtime-host-connection-state.test.ts | 10 +++++++++ .../runtime/runtime-host-connection-state.ts | 7 +++++- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/components/settings/RuntimeEnvironmentsPane.test.ts b/src/renderer/src/components/settings/RuntimeEnvironmentsPane.test.ts index f9fee35e68c..00e3093adc4 100644 --- a/src/renderer/src/components/settings/RuntimeEnvironmentsPane.test.ts +++ b/src/renderer/src/components/settings/RuntimeEnvironmentsPane.test.ts @@ -29,7 +29,9 @@ function details(overrides: Partial): RuntimeHostDetails { } } -function readyTransport(): NonNullable { +function readyTransport( + overrides: Partial> = {} +): NonNullable { return { state: 'ready', pendingRequestCount: 0, @@ -37,7 +39,8 @@ function readyTransport(): NonNullable { reconnectAttempt: 0, lastConnectedAt: 1, lastClose: null, - lastError: null + lastError: null, + ...overrides } } @@ -255,6 +258,21 @@ describe('RuntimeEnvironmentsPane host details', () => { ) }) + it('keeps reconnecting and handshaking failed probes out of disconnected state', () => { + for (const state of ['reconnecting', 'awaiting_ready', 'awaiting_authenticated'] as const) { + expect( + getRuntimeServerConnectionState( + details({ + status: 'error', + remoteControl: readyTransport({ state }), + error: 'runtime.status.get failed' + }) + ), + state + ).toBe(state === 'reconnecting' ? 'reconnecting' : 'checking') + } + }) + it('explains that selecting a saved server is the explicit default Host mode', () => { expect(getActiveServerModeDescription(true)).toContain('Use this computer by default') expect(getActiveServerModeDescription(true)).toContain('browser/mobile handoff') diff --git a/src/renderer/src/components/settings/runtime-environment-host-details.ts b/src/renderer/src/components/settings/runtime-environment-host-details.ts index 5f2e460bb9d..f916f965e56 100644 --- a/src/renderer/src/components/settings/runtime-environment-host-details.ts +++ b/src/renderer/src/components/settings/runtime-environment-host-details.ts @@ -179,10 +179,7 @@ export function getRuntimeServerConnectionState( if (!details || details.status === 'loading') { return 'checking' } - if ( - (details.status !== 'ready' && !isTransportConnected(details)) || - details.compatibility?.kind === 'blocked' - ) { + if (details.compatibility?.kind === 'blocked') { return 'disconnected' } // A compatibility verdict is positive runtime evidence even when an older @@ -195,8 +192,9 @@ export function getRuntimeServerConnectionState( status: details.runtimeStatus, remoteControl: details.remoteControl, // A ready details phase is transport evidence only; status.get may still - // have failed or been omitted by an older peer. - transportStatus: 'connected' + // have failed or been omitted by an older peer. Error details need + // diagnostics to prove transport reachability. + transportStatus: details.status === 'ready' ? 'connected' : 'disconnected' }) } diff --git a/src/renderer/src/runtime/runtime-host-connection-state.test.ts b/src/renderer/src/runtime/runtime-host-connection-state.test.ts index eb2c6469c75..8db0c0eb010 100644 --- a/src/renderer/src/runtime/runtime-host-connection-state.test.ts +++ b/src/renderer/src/runtime/runtime-host-connection-state.test.ts @@ -120,6 +120,16 @@ describe('runtime host connection state', () => { remoteControl: remoteControl({ state: 'reconnecting' }) }) ).toBe('reconnecting') + for (const state of ['awaiting_ready', 'awaiting_authenticated'] as const) { + expect( + runtimeHostConnectionState({ + hasStatusEntry: true, + status: null, + remoteControl: remoteControl({ state }) + }), + state + ).toBe('checking') + } }) it('reports a cleanly closed control channel as disconnected even with no error', () => { diff --git a/src/renderer/src/runtime/runtime-host-connection-state.ts b/src/renderer/src/runtime/runtime-host-connection-state.ts index ed27d51cf0c..6094c995804 100644 --- a/src/renderer/src/runtime/runtime-host-connection-state.ts +++ b/src/renderer/src/runtime/runtime-host-connection-state.ts @@ -48,7 +48,12 @@ export function runtimeHostConnectionState({ return 'reconnecting' } if (!status) { - return transportState === 'connected' ? 'runtime-unavailable' : 'disconnected' + if (transportState === 'connected') { + return 'runtime-unavailable' + } + // The control channel is still negotiating/reconnecting, so the host's + // runtime outcome is not yet knowable. + return transportState === 'checking' ? 'checking' : 'disconnected' } // Why no lastError requirement: a clean close (server restart, host sleep, network // blip) leaves lastError null, and demanding an error string painted those hosts green.