diff --git a/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.test.tsx b/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.test.tsx new file mode 100644 index 000000000000..d5ada5de455b --- /dev/null +++ b/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.test.tsx @@ -0,0 +1,78 @@ +import { renderHook, waitFor } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' + +import type * as SessionStore from '@/store/session' + +import { NEW_CHAT_ROUTE } from '../../routes' + +import { useDesktopIntegrations } from './use-desktop-integrations' + +const sessionMocks = vi.hoisted(() => ({ + getRememberedRoute: vi.fn(() => '/session/stored'), + getRememberedSessionId: vi.fn(() => 'stored-session'), + setRememberedRoute: vi.fn(), + setRememberedSessionId: vi.fn() +})) + +const updatesMocks = vi.hoisted(() => ({ + openUpdatesWindow: vi.fn(), + startUpdatePoller: vi.fn(), + stopUpdatePoller: vi.fn() +})) + +vi.mock('@/store/session', async () => { + const actual = (await vi.importActual('@/store/session')) as typeof SessionStore + + return { + ...actual, + ...sessionMocks + } +}) +vi.mock('@/store/updates', () => updatesMocks) +vi.mock('@/store/windows', () => ({ + isNewSessionWindow: vi.fn(() => true), + isSecondaryWindow: vi.fn(() => false) +})) + +function setHermesDesktopBridge() { + Object.defineProperty(window, 'hermesDesktop', { + configurable: true, + value: { + setPreviewShortcutActive: vi.fn() + } + }) +} + +afterEach(() => { + vi.clearAllMocks() +}) + +describe('useDesktopIntegrations', () => { + it('does not restore remembered session state in a new-session window', async () => { + setHermesDesktopBridge() + const navigate = vi.fn() + + renderHook(() => + useDesktopIntegrations({ + chatOpen: true, + hasPreview: false, + locationPathname: NEW_CHAT_ROUTE, + navigate, + refreshSessions: vi.fn(), + resumeExhaustedSessionId: null, + routedSessionId: null, + runtimeIdByStoredSessionId: { current: new Map() } + }) + ) + + await waitFor(() => { + expect(updatesMocks.startUpdatePoller).toHaveBeenCalledTimes(1) + }) + + expect(sessionMocks.getRememberedRoute).not.toHaveBeenCalled() + expect(sessionMocks.getRememberedSessionId).not.toHaveBeenCalled() + expect(sessionMocks.setRememberedRoute).not.toHaveBeenCalled() + expect(sessionMocks.setRememberedSessionId).not.toHaveBeenCalled() + expect(navigate).not.toHaveBeenCalled() + }) +}) diff --git a/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts b/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts index d0af49665906..594b295ef9d5 100644 --- a/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts +++ b/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts @@ -6,7 +6,7 @@ import { respondToApprovalAction } from '@/store/native-notifications' import { getRememberedRoute, getRememberedSessionId, setRememberedRoute, setRememberedSessionId } from '@/store/session' import { onSessionsChanged } from '@/store/session-sync' import { openUpdatesWindow, startUpdatePoller, stopUpdatePoller } from '@/store/updates' -import { isSecondaryWindow } from '@/store/windows' +import { isNewSessionWindow, isSecondaryWindow } from '@/store/windows' import { requestComposerFocus, requestComposerInsert } from '../../chat/composer/focus' import { appViewForPath, isOverlayView, NEW_CHAT_ROUTE, sessionRoute } from '../../routes' @@ -62,6 +62,10 @@ export function useDesktopIntegrations({ // lands where you were. Overlays (settings/command-center/…) aren't stored — // you don't want to boot into a modal. useEffect(() => { + if (isNewSessionWindow()) { + return + } + if (routedSessionId) { setRememberedSessionId(routedSessionId) } @@ -77,7 +81,7 @@ export function useDesktopIntegrations({ // route (a hidden-then-shown window keeps its own route). Prefer the full // remembered route (covers pages); fall back to the last session id. useEffect(() => { - if (restoredRef.current || locationPathname !== NEW_CHAT_ROUTE) { + if (restoredRef.current || locationPathname !== NEW_CHAT_ROUTE || isNewSessionWindow()) { restoredRef.current = true return