diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index fcfc08b494..2f23c7f5d4 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -1080,6 +1080,7 @@ function openPluginResultView(pluginId: string, contributionId: string, label: s sql: resultSqlForGrid(props.activeTab), result: { columns: result.columns, rows: cappedRows, truncated: result.rows.length > cappedRows.length }, }, + refreshContextOnReuse: true, }); } diff --git a/apps/desktop/src/stores/__tests__/queryStore.openPluginWorkbench.spec.ts b/apps/desktop/src/stores/__tests__/queryStore.openPluginWorkbench.spec.ts index e095346cba..22884c00e4 100644 --- a/apps/desktop/src/stores/__tests__/queryStore.openPluginWorkbench.spec.ts +++ b/apps/desktop/src/stores/__tests__/queryStore.openPluginWorkbench.spec.ts @@ -40,6 +40,40 @@ describe("queryStore openPluginWorkbench reuse", () => { expect(tab?.pluginWorkbench?.context).toEqual({ connectionId: "conn-1", workbenchId: "wb-original" }); }); + it("refreshes a reused result-view context when explicitly requested", () => { + const queryStore = useQueryStore(); + + const firstId = queryStore.openPluginWorkbench("io.dbx.plan-detective", "result-view", { + connectionId: "conn-1", + context: { connectionId: "conn-1", sql: "SELECT 1" }, + }); + const secondId = queryStore.openPluginWorkbench("io.dbx.plan-detective", "result-view", { + connectionId: "conn-1", + context: { connectionId: "conn-1", sql: "SELECT 2" }, + refreshContextOnReuse: true, + }); + + expect(secondId).toBe(firstId); + expect(queryStore.tabs.find((tab) => tab.id === firstId)?.pluginWorkbench?.context).toEqual({ connectionId: "conn-1", sql: "SELECT 2" }); + }); + + it("refreshes a reused result-view result snapshot when explicitly requested", () => { + const queryStore = useQueryStore(); + + const firstId = queryStore.openPluginWorkbench("io.dbx.plan-detective", "result-view", { + connectionId: "conn-1", + context: { connectionId: "conn-1", result: { columns: ["id"], rows: [[1]], truncated: false } }, + }); + const secondId = queryStore.openPluginWorkbench("io.dbx.plan-detective", "result-view", { + connectionId: "conn-1", + context: { connectionId: "conn-1", result: { columns: ["id"], rows: [[2]], truncated: false } }, + refreshContextOnReuse: true, + }); + + expect(secondId).toBe(firstId); + expect(queryStore.tabs.find((tab) => tab.id === firstId)?.pluginWorkbench?.context?.result).toEqual({ columns: ["id"], rows: [[2]], truncated: false }); + }); + it("a different connection still opens its own workbench tab", () => { const queryStore = useQueryStore(); diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts index 00180f81b6..4e10d06a57 100644 --- a/apps/desktop/src/stores/queryStore.ts +++ b/apps/desktop/src/stores/queryStore.ts @@ -3500,16 +3500,24 @@ export const useQueryStore = defineStore("query", () => { return undefined; } - function openPluginWorkbench(pluginId: string, contributionId: string, options: { title?: string; connectionId?: string; database?: string; context?: Record; forceNew?: boolean } = {}) { + function openPluginWorkbench(pluginId: string, contributionId: string, options: { title?: string; connectionId?: string; database?: string; context?: Record; forceNew?: boolean; refreshContextOnReuse?: boolean } = {}) { const contextConnectionId = typeof options.context?.connectionId === "string" ? options.context.connectionId : ""; const connectionId = options.connectionId || contextConnectionId; if (!options.forceNew) { const existing = tabs.value.find((tab) => tab.mode === "plugin-workbench" && tab.pluginWorkbench?.pluginId === pluginId && tab.pluginWorkbench?.contributionId === contributionId && pluginTabConnectionId(tab) === connectionId); if (existing) { - // Reopening surfaces the existing tab as-is. Replacing the context - // here (openPluginConnection mints a fresh workbenchId per click) - // would deep-reload the plugin webview — a full flash plus losing - // the sidecar session binding on the old workbench id. + if (options.refreshContextOnReuse && options.context) { + existing.pluginWorkbench = { + ...existing.pluginWorkbench!, + context: snapshotPluginWorkbenchContext(options.context), + }; + } + // Reopening normally surfaces the existing tab as-is. Replacing the + // context here by default (openPluginConnection mints a fresh + // workbenchId per click) would deep-reload the plugin webview — a full + // flash plus losing the sidecar session binding on the old workbench + // id. Stateless surfaces such as result-view can explicitly opt into + // a context refresh without changing the tab identity. // A tab created by an older build (or otherwise unregistered) may // still be ownerless; land it in the workspace or the group-rendered // tab strips can never show it.