From c03a6a6db244dfc3993880f355b3dbc8adcbcce3 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 11:49:39 -0700 Subject: [PATCH] Clear SSH reset prompt during render (#3389) --- .../settings/SshTargetDestructiveActions.tsx | 29 ++++++++---- .../settings/ssh-target-action-state.test.ts | 46 +++++++++++++++++++ .../settings/ssh-target-action-state.ts | 12 +++++ 3 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 src/renderer/src/components/settings/ssh-target-action-state.test.ts diff --git a/src/renderer/src/components/settings/SshTargetDestructiveActions.tsx b/src/renderer/src/components/settings/SshTargetDestructiveActions.tsx index 9e5ba64f8a6..5c911af8cdc 100644 --- a/src/renderer/src/components/settings/SshTargetDestructiveActions.tsx +++ b/src/renderer/src/components/settings/SshTargetDestructiveActions.tsx @@ -1,9 +1,13 @@ -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useRef, useState } from 'react' import type { ReactNode } from 'react' import type { SshConnectionState } from '../../../../shared/ssh-types' import { useMountedRef } from '@/hooks/useMountedRef' import { SshDestructiveActionDialog } from './SshDestructiveActionDialog' -import { isSshTargetConnecting, type SshTargetBusyAction } from './ssh-target-action-state' +import { + isSshTargetConnecting, + shouldClearPendingSshReset, + type SshTargetBusyAction +} from './ssh-target-action-state' type PendingTargetAction = { id: string; label: string } @@ -102,12 +106,17 @@ export function SshTargetDestructiveActions({ pendingReset !== null && isSshTargetConnecting(pendingResetStatus) const pendingTerminateIsBusy = pendingTerminate !== null && targetActionsInFlight.get(pendingTerminate.id) === 'terminate' - - useEffect(() => { - if (pendingResetBlockedByConnection && !pendingResetIsBusy) { - setPendingReset(null) - } - }, [pendingResetBlockedByConnection, pendingResetIsBusy]) + const shouldClearReset = shouldClearPendingSshReset({ + pendingTargetId: pendingReset?.id ?? null, + pendingResetIsBusy, + connectionStatus: pendingResetStatus + }) + if (shouldClearReset) { + // Why: a reconnecting target cannot safely reset its relay; clear the + // pending dialog before it paints stale destructive UI. + setPendingReset(null) + } + const dialogPendingReset = shouldClearReset ? null : pendingReset const confirmResetRelay = async (): Promise => { if (!pendingReset) { @@ -169,10 +178,10 @@ export function SshTargetDestructiveActions({ /> { + it('classifies connecting statuses as busy connection states', () => { + expect(isSshTargetConnecting('connecting')).toBe(true) + expect(isSshTargetConnecting('deploying-relay')).toBe(true) + expect(isSshTargetConnecting('reconnecting')).toBe(true) + expect(isSshTargetConnecting('connected')).toBe(false) + expect(isSshTargetConnecting('disconnected')).toBe(false) + }) + + it('clears pending reset only when a non-busy target starts connecting', () => { + expect( + shouldClearPendingSshReset({ + pendingTargetId: 'target-1', + pendingResetIsBusy: false, + connectionStatus: 'reconnecting' + }) + ).toBe(true) + + expect( + shouldClearPendingSshReset({ + pendingTargetId: 'target-1', + pendingResetIsBusy: true, + connectionStatus: 'reconnecting' + }) + ).toBe(false) + + expect( + shouldClearPendingSshReset({ + pendingTargetId: null, + pendingResetIsBusy: false, + connectionStatus: 'reconnecting' + }) + ).toBe(false) + + expect( + shouldClearPendingSshReset({ + pendingTargetId: 'target-1', + pendingResetIsBusy: false, + connectionStatus: 'connected' + }) + ).toBe(false) + }) +}) diff --git a/src/renderer/src/components/settings/ssh-target-action-state.ts b/src/renderer/src/components/settings/ssh-target-action-state.ts index 271dc9ae203..38db0cdbf28 100644 --- a/src/renderer/src/components/settings/ssh-target-action-state.ts +++ b/src/renderer/src/components/settings/ssh-target-action-state.ts @@ -11,3 +11,15 @@ const SSH_TARGET_CONNECTING_STATUSES: ReadonlySet = new Set export function isSshTargetConnecting(status: SshConnectionStatus): boolean { return SSH_TARGET_CONNECTING_STATUSES.has(status) } + +export function shouldClearPendingSshReset({ + pendingTargetId, + pendingResetIsBusy, + connectionStatus +}: { + pendingTargetId: string | null + pendingResetIsBusy: boolean + connectionStatus: SshConnectionStatus +}): boolean { + return pendingTargetId !== null && !pendingResetIsBusy && isSshTargetConnecting(connectionStatus) +}