diff --git a/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx b/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx index 2db75c8bfe880..3d1d8f9c0124d 100644 --- a/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx +++ b/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx @@ -2,6 +2,7 @@ import { act, cleanup, render } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { $desktopBoot } from '@/store/boot' +import { $activeGatewayProfile } from '@/store/profile' import { $gatewayState } from '@/store/session' import { useGatewayBoot } from './use-gateway-boot' @@ -122,6 +123,7 @@ beforeEach(() => { FakeWebSocket.instances = [] ;(globalThis as { WebSocket: unknown }).WebSocket = FakeWebSocket ;(window as { hermesDesktop?: unknown }).hermesDesktop = fakeDesktop() + $activeGatewayProfile.set('default') $gatewayState.set('idle') $desktopBoot.set({ error: null, @@ -224,6 +226,18 @@ describe('useGatewayBoot remote reconnect loop (real hook, fake socket)', () => expect(FakeWebSocket.instances.length).toBeGreaterThan(1) }) + it('seeds the primary gateway profile from the desktop preference before connecting', async () => { + const desktop = fakeDesktop() + desktop.profile.get = vi.fn(async () => ({ profile: 'coder' })) + ;(window as { hermesDesktop?: unknown }).hermesDesktop = desktop + + render() + await flushAsync() + + expect($activeGatewayProfile.get()).toBe('coder') + expect(desktop.profile.get).toHaveBeenCalled() + }) + it('FIX: after the prolonged drop the hook raises a recoverable boot error (the escape hatch)', async () => { render() await flushAsync() diff --git a/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts b/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts index 593e7a36f748d..6e87adb2067c6 100644 --- a/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts +++ b/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts @@ -222,6 +222,20 @@ export function useGatewayBoot({ // Secondary (background-profile) sockets funnel into the same handler. configureGatewayRegistry({ onEvent: event => callbacksRef.current.handleGatewayEvent(event) }) + const seedPrimaryProfileFromPreference = async () => { + try { + const pref = await desktop.profile?.get?.() + const profileKey = normalizeProfileKey(pref?.profile) + + $activeGatewayProfile.set(profileKey) + setPrimaryGateway(gateway, profileKey) + + return profileKey + } catch { + return null + } + } + const offState = gateway.onState(st => { // Mirror to the composer only while the primary is the active profile — // a background secondary reconnect mustn't flip the foreground state. @@ -315,6 +329,7 @@ export function useGatewayBoot({ async function boot() { try { + await seedPrimaryProfileFromPreference() const conn = await desktop.getConnection() if (cancelled) { @@ -343,10 +358,7 @@ export function useGatewayBoot({ // same-profile resumes are no-op swaps and any reconnect targets the // right backend. Best-effort: a missing preference means "default". try { - const pref = await desktop.profile?.get?.() - const profileKey = (pref?.profile ?? '').trim() || 'default' - $activeGatewayProfile.set(profileKey) - setPrimaryGateway(gateway, profileKey) + const profileKey = (await seedPrimaryProfileFromPreference()) || 'default' void ensureGatewayForProfile(profileKey) } catch { $activeGatewayProfile.set('default')