From 400ff6dd0219600f20b45ae01c39424e46b091c1 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Mon, 14 Sep 2026 22:53:49 +0800 Subject: [PATCH] fix(plugins): restore workbench refresh and bootstrap --- apps/desktop/src/App.vue | 32 ++++++++++++++++++- .../src/components/layout/AppTabBar.vue | 3 -- .../src/lib/plugins/pluginHostBridge.spec.ts | 19 +++++++++-- .../src/lib/plugins/pluginHostBridge.ts | 1 - 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 0f537d6160..f75523371c 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -421,6 +421,8 @@ const activeTab = computed(() => queryStore.tabs.find((t) => t.id === queryStore // iframe moved out of the DOM reloads from scratch, so KeepAlive/ContentArea // remounts flash the whole webview and drop its live session state. const mountedPluginWorkbenchTabs = computed(() => queryStore.tabs.filter((tab) => tab.mode === "plugin-workbench" && tab.pluginWorkbench)); +type PluginWorkbenchTabHandle = { refresh: () => Promise }; +const pluginWorkbenchTabRefs = new Map(); const tabNavigationHistory = ref(createTabNavigationHistory()); let pendingTabHistoryNavigationId: string | null = null; let detachedEventUnlisteners: Array<() => void> = []; @@ -3144,6 +3146,23 @@ function handleNativeSelectAll(e: KeyboardEvent) { if (shouldBlockAppNativeSelectAll(e)) e.preventDefault(); } +function setPluginWorkbenchTabRef(tabId: string, element: unknown) { + if (element && typeof element === "object" && "refresh" in element && typeof element.refresh === "function") { + pluginWorkbenchTabRefs.set(tabId, element as PluginWorkbenchTabHandle); + } else { + pluginWorkbenchTabRefs.delete(tabId); + } +} + +function refreshActivePluginWorkbench(): boolean { + const tab = activeTab.value; + if (tab?.mode !== "plugin-workbench") return false; + const pluginWorkbench = pluginWorkbenchTabRefs.get(tab.id); + if (!pluginWorkbench) return false; + void pluginWorkbench.refresh(); + return true; +} + async function closeActiveSurface() { if (showSettingsPage.value) { closeSettingsPage(); @@ -3303,6 +3322,11 @@ async function handleKeydown(e: KeyboardEvent) { if (selectedSql.value.trim()) sendSelectionToAi(selectedSql.value); return; } + if (isModRShortcut(e) && refreshActivePluginWorkbench()) { + e.preventDefault(); + e.stopPropagation(); + return; + } if (isModRShortcut(e) && e.target instanceof Element && contentAreaRef.value?.handleModRTarget(e.target)) { e.preventDefault(); e.stopPropagation(); @@ -3876,7 +3900,13 @@ onUnmounted(() => { yields the layout (display:none) while a plugin tab is active, or both flex-1 siblings would split the column. -->
- +
diff --git a/apps/desktop/src/components/layout/AppTabBar.vue b/apps/desktop/src/components/layout/AppTabBar.vue index 1506e359e6..d794287e50 100644 --- a/apps/desktop/src/components/layout/AppTabBar.vue +++ b/apps/desktop/src/components/layout/AppTabBar.vue @@ -205,9 +205,6 @@ function handleCancelClose() {
-
diff --git a/apps/desktop/src/lib/plugins/pluginHostBridge.spec.ts b/apps/desktop/src/lib/plugins/pluginHostBridge.spec.ts index e6a011cf35..544827e318 100644 --- a/apps/desktop/src/lib/plugins/pluginHostBridge.spec.ts +++ b/apps/desktop/src/lib/plugins/pluginHostBridge.spec.ts @@ -384,12 +384,19 @@ describe("plugin SDK source", () => { dbxPlugin?: { invoke: (method: string, params?: unknown, options?: { timeoutMs?: number }) => Promise }; } - function loadSdk(posted: unknown[]): SdkWindow { + function loadSdk(posted: unknown[], initialTheme?: { appearance: "dark" | "light"; tokens: Record }): SdkWindow { const sandbox = {} as SdkWindow; + const document = { + documentElement: { + dataset: {} as Record, + style: { colorScheme: "", setProperty: vi.fn() }, + }, + dispatchEvent: vi.fn(), + } as unknown as Document; // The SDK IIFE only touches window and the bare-global addEventListener at // boot; parent.postMessage is captured for later request() calls, so a // stub window/parent is enough here. - new Function("window", "parent", "addEventListener", pluginSdkSource())(sandbox, { postMessage: (message: unknown) => posted.push(message) }, () => {}); + new Function("window", "parent", "addEventListener", "document", pluginSdkSource(initialTheme))(sandbox, { postMessage: (message: unknown) => posted.push(message) }, () => {}, document); return sandbox; } @@ -421,6 +428,14 @@ describe("plugin SDK source", () => { expect(JSON.stringify(message.params.params.sessionIds)).toBe(JSON.stringify(["session-a", "session-b"])); }); + it("boots successfully when the host provides the initial theme", () => { + const posted: unknown[] = []; + const { dbxPlugin } = loadSdk(posted, { appearance: "dark", tokens: { "--color-background": "#131416" } }); + + expect(dbxPlugin).toBeDefined(); + expect(posted[0]).toMatchObject({ source: "dbx-plugin", type: "ready" }); + }); + it("keeps cloneable values intact and plain scalars by reference", () => { const posted: unknown[] = []; const { dbxPlugin } = loadSdk(posted); diff --git a/apps/desktop/src/lib/plugins/pluginHostBridge.ts b/apps/desktop/src/lib/plugins/pluginHostBridge.ts index 208c4bde91..12a1bc6947 100644 --- a/apps/desktop/src/lib/plugins/pluginHostBridge.ts +++ b/apps/desktop/src/lib/plugins/pluginHostBridge.ts @@ -349,7 +349,6 @@ export function pluginSdkSource(initialTheme?: PluginBridgeTheme): string { const applyTheme = (value) => { if (!value || typeof value !== 'object') return; theme = value; - const theme = value; const root = document.documentElement; root.dataset.dbxTheme = theme.appearance === "dark" ? "dark" : "light"; root.style.colorScheme = theme.appearance === "dark" ? "dark" : "light";