From 04c91add22d518f2ed5bcf5a6b1b69ea35044cd9 Mon Sep 17 00:00:00 2001 From: sudacode Date: Mon, 26 Oct 2026 22:57:45 -0700 Subject: [PATCH] fix(plugins): skip modal teardown for stale plugin panel closes handleModalClosed now reports whether teardown should continue. A late close notice for a replaced panel returns before the generic modal-close flow, so it no longer hides the modal window showing the newer panel. --- src/main.ts | 11 ++++++++--- src/main/runtime/plugin-panel-runtime.test.ts | 12 +++++++++--- src/main/runtime/plugin-panel-runtime.ts | 12 +++++++++--- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main.ts b/src/main.ts index 5411bc93..0f4606d3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6040,12 +6040,17 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({ } }, onOverlayModalClosed: (modal, senderWindow, instanceId) => { + // A late close for a replaced plugin panel must not hide the panel now showing. + if ( + modal === 'plugin-panel' && + instanceId !== undefined && + !pluginPanelRuntime.handleModalClosed(instanceId) + ) { + return; + } if (modal === 'media-timing-review') { void mediaTimingReviewRuntime.dispose(); } - if (modal === 'plugin-panel' && instanceId !== undefined) { - pluginPanelRuntime.handleModalClosed(instanceId); - } if (modal === 'subtitle-sidebar' && senderWindow === overlayManager.getMainWindow()) { subtitleSidebarRequestedOpen = false; } diff --git a/src/main/runtime/plugin-panel-runtime.test.ts b/src/main/runtime/plugin-panel-runtime.test.ts index 9076d344..cb70ede3 100644 --- a/src/main/runtime/plugin-panel-runtime.test.ts +++ b/src/main/runtime/plugin-panel-runtime.test.ts @@ -44,7 +44,7 @@ test('closing from the overlay aborts the handle and ignores later updates', asy await new Promise((resolve) => setImmediate(resolve)); const sentBefore = h.sent.length; - h.runtime.handleModalClosed(1); + assert.equal(h.runtime.handleModalClosed(1), true); panel.update({ markdown: 'too late' }); assert.equal(panel.signal.aborted, true); @@ -58,11 +58,17 @@ test('a late close acknowledgement for a replaced panel leaves the new panel ope first.close(); const second = h.runtime.open({ title: 'Two', markdown: '' }); - h.runtime.handleModalClosed(1); - + assert.equal(h.runtime.handleModalClosed(1), false, 'skip teardown of the newer panel'); assert.equal(second.signal.aborted, false); }); +test('a close notice after the plugin closed its panel still tears the modal down', () => { + const h = createHarness(Promise.resolve(true)); + h.runtime.open({ title: 'One', markdown: '' }).close(); + + assert.equal(h.runtime.handleModalClosed(1), true); +}); + test('opening a new panel aborts the previous one', () => { const h = createHarness(Promise.resolve(true)); const first = h.runtime.open({ title: 'One', markdown: '' }); diff --git a/src/main/runtime/plugin-panel-runtime.ts b/src/main/runtime/plugin-panel-runtime.ts index 36894ece..614520bc 100644 --- a/src/main/runtime/plugin-panel-runtime.ts +++ b/src/main/runtime/plugin-panel-runtime.ts @@ -103,9 +103,15 @@ export function createPluginPanelRuntime(deps: { }; }, - /** The overlay closed `panelId`; a late notice for an already replaced panel is ignored. */ - handleModalClosed(panelId: number): void { - if (active?.panelId === panelId) finish(active); + /** + * The overlay closed `panelId`. Returns false for a late notice about a panel that a newer one + * already replaced, so the caller skips the modal teardown that would hide the newer panel. + */ + handleModalClosed(panelId: number): boolean { + if (!active) return true; + if (active.panelId !== panelId) return false; + finish(active); + return true; }, }; }