From f3f700d11c94c1f306987cda0df4916e264f7543 Mon Sep 17 00:00:00 2001 From: ThunderTr77 Date: Mon, 15 Jun 2026 19:38:48 +0700 Subject: [PATCH 1/2] fix: hide model dropdown popup on unmount --- .../composables/useModelDropdownPopup.ts | 17 +++++++++++++ .../SearchView/useModelDropdownPopup.test.ts | 25 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/apps/desktop/src/views/SearchView/composables/useModelDropdownPopup.ts b/apps/desktop/src/views/SearchView/composables/useModelDropdownPopup.ts index 40b03872..1a95627d 100644 --- a/apps/desktop/src/views/SearchView/composables/useModelDropdownPopup.ts +++ b/apps/desktop/src/views/SearchView/composables/useModelDropdownPopup.ts @@ -204,12 +204,29 @@ export function useModelDropdownPopup(options: UseModelDropdownPopupOptions) { onUnmounted(() => { disposed = true; + const closingIdentity = + activePopupId !== null + ? { + popupId: activePopupId, + windowLabel: 'popup-model-dropdown-popup', + popupSessionVersion: parsePopupSessionVersion(activePopupId), + } + : null; + const shouldHidePopup = hasActivePopupSession || isOpen.value; cleanupFn?.(); cleanupFn = null; activePopupId = null; hasActivePopupSession = false; isOpen.value = false; onPopupSessionEnd?.(); + + if (!shouldHidePopup || !closingIdentity) { + return; + } + + void popupManager.hide(closingIdentity).catch((error) => { + console.error('[SearchView] Failed to hide model dropdown popup on unmount:', error); + }); }); /** diff --git a/apps/desktop/tests/composables/SearchView/useModelDropdownPopup.test.ts b/apps/desktop/tests/composables/SearchView/useModelDropdownPopup.test.ts index 2a02409e..0b1443fc 100644 --- a/apps/desktop/tests/composables/SearchView/useModelDropdownPopup.test.ts +++ b/apps/desktop/tests/composables/SearchView/useModelDropdownPopup.test.ts @@ -216,6 +216,31 @@ describe('useModelDropdownPopup', () => { mounted.unmount(); }); + it('hides the current popup session on unmount when the popup is still open', async () => { + const onPopupSessionEnd = vi.fn(); + const mounted = await mountComposable(() => + useModelDropdownPopup({ + getAnchorElement: () => document.createElement('button'), + getPopupData: () => createPopupData(), + isModelDropdownActive: () => true, + onModelSelect: () => undefined, + onModelSearchQueryChange: () => undefined, + onClose: () => undefined, + onPopupSessionEnd, + }) + ); + + await mounted.result.open(); + mounted.unmount(); + + expect(popupManager.hide).toHaveBeenCalledWith({ + popupId: 'popup-model-dropdown-popup:1', + popupSessionVersion: 1, + windowLabel: 'popup-model-dropdown-popup', + }); + expect(onPopupSessionEnd).toHaveBeenCalledTimes(1); + }); + it('closes the active popup session with the current identity', async () => { const onPopupSessionEnd = vi.fn(); const mounted = await mountComposable(() => From 594532ce3971382ccf34d6101e8368c69f2e38d5 Mon Sep 17 00:00:00 2001 From: ThunderTr77 Date: Tue, 28 Jul 2026 15:03:12 +0700 Subject: [PATCH 2/2] fix(search): close popup opened after unmount --- .../composables/useModelDropdownPopup.ts | 13 +++++++ .../SearchView/useModelDropdownPopup.test.ts | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/apps/desktop/src/views/SearchView/composables/useModelDropdownPopup.ts b/apps/desktop/src/views/SearchView/composables/useModelDropdownPopup.ts index 1a95627d..58d3043d 100644 --- a/apps/desktop/src/views/SearchView/composables/useModelDropdownPopup.ts +++ b/apps/desktop/src/views/SearchView/composables/useModelDropdownPopup.ts @@ -90,6 +90,19 @@ export function useModelDropdownPopup(options: UseModelDropdownPopupOptions) { popupSessionVersion: parsePopupSessionVersion(popupId), } : null; + + if (disposed) { + if (identity) { + await popupManager.hide(identity).catch((error) => { + console.error( + '[SearchView] Failed to hide model dropdown popup on unmount:', + error + ); + }); + } + return; + } + const isLivePopupSession = identity ? isLiveModelDropdownPopupSession(identity) : false; if (!identity || !isLivePopupSession) { diff --git a/apps/desktop/tests/composables/SearchView/useModelDropdownPopup.test.ts b/apps/desktop/tests/composables/SearchView/useModelDropdownPopup.test.ts index 0b1443fc..de9912b5 100644 --- a/apps/desktop/tests/composables/SearchView/useModelDropdownPopup.test.ts +++ b/apps/desktop/tests/composables/SearchView/useModelDropdownPopup.test.ts @@ -241,6 +241,40 @@ describe('useModelDropdownPopup', () => { expect(onPopupSessionEnd).toHaveBeenCalledTimes(1); }); + it('closes a popup that finishes opening after the composable unmounts', async () => { + let resolveShow: ((popupId: string) => void) | undefined; + vi.mocked(popupManager.show).mockImplementation( + () => + new Promise((resolve) => { + resolveShow = resolve; + }) + ); + const onPopupSessionStart = vi.fn(); + const mounted = await mountComposable(() => + useModelDropdownPopup({ + getAnchorElement: () => document.createElement('button'), + getPopupData: () => createPopupData(), + isModelDropdownActive: () => true, + onModelSelect: () => undefined, + onModelSearchQueryChange: () => undefined, + onClose: () => undefined, + onPopupSessionStart, + }) + ); + + const opening = mounted.result.open(); + mounted.unmount(); + resolveShow?.('popup-model-dropdown-popup:1'); + await opening; + + expect(popupManager.hide).toHaveBeenCalledWith({ + popupId: 'popup-model-dropdown-popup:1', + windowLabel: 'popup-model-dropdown-popup', + popupSessionVersion: expect.any(Number), + }); + expect(onPopupSessionStart).not.toHaveBeenCalled(); + }); + it('closes the active popup session with the current identity', async () => { const onPopupSessionEnd = vi.fn(); const mounted = await mountComposable(() =>