fix(desktop): wait 5m before reconnect warning toast

Brief transport blips often self-heal in 1–3 minutes. Raise the
non-blocking escalate toast from 45s to 5m so those windows stay quiet
while chat remains readable/draftable. Confirmed reauth still takes the
full-screen recovery overlay immediately.
This commit is contained in:
Andrew Fiebert
2026-08-10 09:45:10 -04:00
committed by Teknium
parent f2c204d689
commit 8db59d08f4
3 changed files with 21 additions and 17 deletions
@@ -372,8 +372,9 @@ describe('useGatewayBoot remote reconnect loop (real hook, fake socket)', () =>
act(() => FakeWebSocket.instances[0].drop())
await flushAsync()
// Walk the backoff past the ~45s escalation threshold.
for (let i = 0; i < 8; i += 1) {
// Walk the backoff well past the historical 45s threshold and into the
// current multi-minute escalate window. Chat must stay unlocked either way.
for (let i = 0; i < 24; i += 1) {
await advanceBackoff()
}
@@ -393,7 +394,7 @@ describe('useGatewayBoot remote reconnect loop (real hook, fake socket)', () =>
act(() => FakeWebSocket.instances[0].drop())
await flushAsync()
for (let i = 0; i < 8; i += 1) {
for (let i = 0; i < 24; i += 1) {
await advanceBackoff()
}
@@ -64,11 +64,15 @@ import { stashGatewaySurvivor, survivorIsStale, takeGatewaySurvivor } from './ga
// After the reconnect loop has been failing for this long, raise a NON-blocking
// warning toast. Full-screen BootFailureOverlay used to lock the user out of
// reading/drafting for the whole 1–3 minute blip even though the transcript is
// still on screen underneath. Confirmed reauth still escalates to the overlay
// (Sign in is required). Time-based (not attempt-count) because full-jitter
// backoff makes attempt counts a meaningless clock.
const RECONNECT_ESCALATE_AFTER_MS = 45_000
// reading/drafting for the whole blip even though the transcript is still on
// screen underneath. Confirmed reauth still escalates to the overlay (Sign in
// is required). Time-based (not attempt-count) because full-jitter backoff
// makes attempt counts a meaningless clock.
//
// 5 minutes (not the historical ~45s) so brief transport weather — ticket mint
// flaps, sleep/wake, Wi‑Fi blips that self-heal in 1–3 minutes — never even
// toast. Chat stays readable/draftable the whole time either way.
const RECONNECT_ESCALATE_AFTER_MS = 300_000
// Bounded self-heal for a failed REMOTE boot (#82679): when the primary boot
// fails on a transient remote fault (dropped SSH/HTTP registered connection,
@@ -158,9 +162,8 @@ export function useGatewayBoot({
// identical error toasts (and their haptics). Reset on the next clean open.
let reauthNotified = false
// Raised once the reconnect loop has been failing for
// RECONNECT_ESCALATE_AFTER_MS so the recovery overlay replaces the
// dead-end CONNECTING screen. Reset on a clean open or a manual/
// wake-driven reconnect.
// RECONNECT_ESCALATE_AFTER_MS so we fire a single non-blocking toast.
// Reset on a clean open or a manual/wake-driven reconnect.
let escalated = false
// Bounded automatic boot retry for transient REMOTE failures (#82679).
let bootRetryAttempt = 0
@@ -170,14 +170,14 @@ describe('connecting overlay vs recovery surface', () => {
expect(isRecoveryShown()).toBe(false)
})
it('FIX: once the prolonged reconnect raises a recoverable boot error, the recovery overlay takes over', async () => {
// Mirrors what useGatewayBoot.scheduleReconnect() now does after ~45s of
// failed post-boot reconnects: it calls failDesktopBoot(), flipping the UI
// from the dead-end CONNECTING overlay to the recovery surface.
it('FIX: confirmed reauth boot.error still surfaces the recovery overlay (not CONNECTING)', async () => {
// Transport blips no longer set boot.error (toast + background retry).
// Confirmed OAuth reauth still does — and that recovery surface must win
// over any residual connecting state so Sign-in / Gateway settings stay reachable.
setGatewayState('error')
$desktopBoot.set({
...$desktopBoot.get(),
error: 'Lost connection to the Hermes gateway and could not reconnect.',
error: 'Your remote gateway session has expired. Sign in again.',
running: false,
visible: true
})
@@ -191,7 +191,7 @@ describe('connecting overlay vs recovery surface', () => {
)
})
// Escape hatch is now reachable; the connecting overlay bows out.
// Escape hatch is reachable; the connecting overlay bows out.
expect(isRecoveryShown()).toBe(true)
expect(screen.getByRole('button', { name: /gateway settings/i })).toBeTruthy()
expect(isConnectingShown()).toBe(false)