Repository navigation
fix(desktop): close plugin panel on reload and clean up failed panel loads - #1198
Conversation
…loads Ensure detached plugin panels are closed when a development plugin reloads, and clean up uninitialized windows if loadURL fails. Previously, onPluginReloaded dropped docked views via closePlugin but did not close detached panel windows, leaving stale window instances across reloads. Additionally, loadURL errors in PluginPanelHost left half-initialized window state, causing initial open requests to flash or disappear. Fixes vastsa#1170
|
I confirmed the reported lifecycle problem is real, but this patch is not root-complete, so I am not merging it. |
Serialize openPanel and close operations per plugin ID to prevent races, force window destruction on reload when beforeunload refuses to close, and await panel teardown before emitting reload completion. Previously, onPluginReloaded initiated close without awaiting it, allowing subsequent openPanel calls to race against the in-flight destruction. Furthermore, windows that refused to close (or stalled in beforeunload) were retained, allowing stale panel windows to survive reloads. Now, PanelOperationSerializer serializes open and close transitions, onPluginReloaded awaits close with force: true, and refused closes are forcibly destroyed. Fixes vastsa#1170
|
Thanks for the sharp and insightful review on the lifecycle race! You pinpointed the exact issue with unawaited teardowns and refused closes. I've fully completed the implementation and added dynamic behavioral test coverage:
All 45 panel, reload, and invoker tests pass cleanly. Ready for re-review! |
Summary
Fixes #1170 by ensuring detached plugin panels are torn down during plugin reload, and cleaning up half-initialized windows if
loadURLfails during initial open.Motivation & Root Cause
In #1170, when developing plugins exposing
ui.openPanel(e.g. Token Insights), opening a panel for the first time after a plugin reload caused the panel to flash or disappear, requiring a second open request to succeed.Two underlying defects contributed to this lifecycle failure:
apps/desktop/electron/main/services/plugin-services.ts,onPluginReloadedcalledpluginViews.closePlugin(pluginId)to drop docked views, but omitted closing floating panel windows viapluginPanels.close(pluginId). The old window and its associated webContents lingered in host state across reloads.apps/desktop/electron/main/plugin-panel-host.ts,win.loadURL(...)lacked an error handler. When an initial load failed or was interrupted, the window was left registered inthis.windowsin an uninitialized state rather than being cleaned up deterministically.Key Changes
apps/desktop/electron/main/services/plugin-services.ts:void pluginPanels.close(pluginId)inonPluginReloadedalongsidepluginViews.closePlugin(pluginId).apps/desktop/electron/main/plugin-panel-host.ts:win.loadURL(...)in atry / catchblock. If loading fails, any surviving half-initialized window is destroyed viawin.destroy()to ensure subsequent open requests are independent and clean.apps/desktop/test/plugin-hot-reload.test.mjs:pluginPanels.close(pluginId)is called on development plugin reload.apps/desktop/test/plugin-panel-window.test.mjs:Verification
node --test test/plugin-hot-reload.test.mjs: 7/7 passed.node --test test/plugin-panel-window.test.mjs: 9/9 passed.node --test test/plugin-panel-invoker.test.mjs: 8/8 passed.node --test test/plugin-work-panel-views.test.mjs: 16/16 passed.pnpm lint: Checked 99 files, style tokens OK.pnpm check:pr-base: Passed against upstreammain.