fix(remote): preserve reconnecting runtime state

This commit is contained in:
Merge Sim
2026-08-31 12:57:46 -07:00
parent 1fb8a8003f
commit 76b98391a1
4 changed files with 40 additions and 9 deletions
@@ -29,7 +29,9 @@ function details(overrides: Partial<RuntimeHostDetails>): RuntimeHostDetails {
}
}
function readyTransport(): NonNullable<RuntimeHostDetails['remoteControl']> {
function readyTransport(
overrides: Partial<NonNullable<RuntimeHostDetails['remoteControl']>> = {}
): NonNullable<RuntimeHostDetails['remoteControl']> {
return {
state: 'ready',
pendingRequestCount: 0,
@@ -37,7 +39,8 @@ function readyTransport(): NonNullable<RuntimeHostDetails['remoteControl']> {
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')
@@ -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'
})
}
@@ -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', () => {
@@ -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.