fix(runtime): suppress outage toast on disconnect

This commit is contained in:
Brennan Benson
2026-07-31 13:01:25 -07:00
parent cdeb993db3
commit 1ecc0b3c3e
4 changed files with 41 additions and 8 deletions
@@ -592,10 +592,14 @@ export function RuntimeEnvironmentsPane({
await window.api.runtimeEnvironments.disconnect({ selector: environment.id })
// Why: disconnect is non-destructive; keep the saved server but show the
// user that this live client is no longer attached to it.
useAppStore.getState().setRuntimeEnvironmentStatus(environment.id, {
status: null,
checkedAt: Date.now()
})
useAppStore.getState().setRuntimeEnvironmentStatus(
environment.id,
{
status: null,
checkedAt: Date.now()
},
{ suppressDisconnectToast: true }
)
if (mountedRef.current) {
setDetailsByEnvironmentId((current) => ({
...current,
@@ -254,7 +254,11 @@ export function SshStatusSegment({
async (environmentId: string): Promise<void> => {
try {
await window.api.runtimeEnvironments.disconnect({ selector: environmentId })
setRuntimeEnvironmentStatus(environmentId, { status: null, checkedAt: Date.now() })
setRuntimeEnvironmentStatus(
environmentId,
{ status: null, checkedAt: Date.now() },
{ suppressDisconnectToast: true }
)
recordFeatureInteraction('ssh')
} catch (err) {
toast.error(
@@ -240,6 +240,22 @@ describe('runtime-status slice', () => {
expect(toast.warning).not.toHaveBeenCalled()
})
it('does not report an intentional disconnect as an outage', () => {
const store = createSliceStore()
store.getState().setRuntimeEnvironmentStatus('env-a', { status: makeStatus(), checkedAt: 1 })
store
.getState()
.setRuntimeEnvironmentStatus(
'env-a',
{ status: null, checkedAt: 2 },
{ suppressDisconnectToast: true }
)
expect(toast.warning).not.toHaveBeenCalled()
expect(store.getState().runtimeStatusByEnvironmentId.get('env-a')?.status).toBeNull()
})
it('clears a single environment entry', () => {
const store = createSliceStore()
store.getState().setRuntimeEnvironmentStatus('env-a', { status: makeStatus(), checkedAt: 1 })
@@ -46,7 +46,11 @@ export type RuntimeStatusSlice = {
* retires state owned by any environment that just left the saved list. */
setRuntimeEnvironments: (environments: PublicKnownRuntimeEnvironment[]) => void
/** Merges one environment's status. Replaces the prior entry for that id. */
setRuntimeEnvironmentStatus: (environmentId: string, status: RuntimeEnvironmentStatus) => void
setRuntimeEnvironmentStatus: (
environmentId: string,
status: RuntimeEnvironmentStatus,
options?: { suppressDisconnectToast?: boolean }
) => void
/** Drops a removed environment so stale hosts don't linger in the registry. */
clearRuntimeEnvironmentStatus: (environmentId: string) => void
/** Drops every entry whose id is not in the saved-environments set. */
@@ -223,7 +227,7 @@ export const createRuntimeStatusSlice: StateCreator<AppState, [], [], RuntimeSta
}
},
setRuntimeEnvironmentStatus: (environmentId, status) => {
setRuntimeEnvironmentStatus: (environmentId, status, options) => {
const previous = get().runtimeStatusByEnvironmentId.get(environmentId)
// Why: a non-null status proves the runtime just answered, so drop any stale
// "offline" compat failure before this online transition fires the
@@ -249,7 +253,12 @@ export const createRuntimeStatusSlice: StateCreator<AppState, [], [], RuntimeSta
})
if (previous?.status === null && status.status !== null) {
dismissRuntimeDisconnectedToast(environmentId)
} else if (previous && previous.status !== null && status.status === null) {
} else if (
previous &&
previous.status !== null &&
status.status === null &&
!options?.suppressDisconnectToast
) {
showRuntimeDisconnectedToast(environmentId, get)
}
},