diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index f0ca23d2ab..a71a4ae3bd 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -3509,6 +3509,7 @@ onUnmounted(() => { class="flex-1 min-h-0" @update:open="(open: boolean) => (open ? activateSettingsPage() : closeSettingsPage())" @check-updates="checkUpdates()" + @ai-config-deep-link-handled="settingsAiConfigDraft = null" />
(); const isSettingsPage = computed(() => props.variant === "page"); @@ -3966,6 +3967,7 @@ async function applyPendingAiConfigDeepLinkDraft() { if (!settingsVisible.value || !requestId || requestId === handledAiConfigRequestId || !draft) return; handledAiConfigRequestId = requestId; + emit("ai-config-deep-link-handled"); activeSettingsTab.value = "ai"; aiEnterEditMode(); aiEditConfigName.value = draft.name; diff --git a/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.aiConfigDeepLinkTabHijack.spec.ts b/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.aiConfigDeepLinkTabHijack.spec.ts new file mode 100644 index 0000000000..1855b85ed6 --- /dev/null +++ b/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.aiConfigDeepLinkTabHijack.spec.ts @@ -0,0 +1,35 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const dialogSource = readFileSync(new URL("../EditorSettingsDialog.vue", import.meta.url), "utf8"); +const appSource = readFileSync(new URL("../../../App.vue", import.meta.url), "utf8"); + +// Regression for https://github.com/t8y2/dbx/issues/7874: after using an AI +// config deep link (dbx://settings/ai/new?...) once, every later "open +// Settings" (even a plain click on the gear icon) got forcibly yanked back to +// the AI tab. Root cause: `settingsAiConfigDraft`/`settingsAiConfigRequestId` +// in App.vue are set once by `openAiConfigDeepLink` and never cleared, while +// the dialog's own "already handled this request" guard +// (`handledAiConfigRequestId`) is a plain local variable that resets to 0 +// every time the dialog remounts (it's `v-if`-toggled in App.vue, so closing +// Settings destroys the instance). On the next open, `requestId !== +// handledAiConfigRequestId` is true again even though nothing new happened, +// so `applyPendingAiConfigDeepLinkDraft` reapplies the stale draft and jumps +// the user back into "ai" tab / edit mode forever, once per app run. +describe("EditorSettingsDialog AI config deep-link tab hijack", () => { + function applyDraftBlock(): string { + const start = dialogSource.indexOf("async function applyPendingAiConfigDeepLinkDraft()"); + const end = dialogSource.indexOf("\n}\n", start); + if (start < 0 || end < 0) throw new Error("Missing applyPendingAiConfigDeepLinkDraft function block"); + return dialogSource.slice(start, end); + } + + it("emits an event once the deep-link draft has been consumed", () => { + expect(dialogSource).toContain('"ai-config-deep-link-handled": []'); + expect(applyDraftBlock()).toContain('emit("ai-config-deep-link-handled")'); + }); + + it("App.vue clears the draft/request state once notified, so a remounted dialog can't reapply it", () => { + expect(appSource).toContain('@ai-config-deep-link-handled="settingsAiConfigDraft = null"'); + }); +});