diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 6675d02fe8..a572df84ca 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -21,6 +21,7 @@ import { CONNECTION_ATTEMPT_CANCELLED_MESSAGE, useConnectionStore } from "@/stor import { useTunnelProfileStore } from "@/stores/tunnelProfileStore"; import { detachTunnelProfileLayer, tunnelProfileReferenceLayer, tunnelProfileSummary } from "@/lib/connection/tunnelProfiles"; import { applySshConfigHostAliasPrefill as prefillSshConfigHostAlias } from "@/lib/connection/sshConfigHosts"; +import { connectionEditDraftSyncAction } from "./connectionEditDraftSync"; import { REDIS_SCAN_PAGE_SIZE_DEFAULT, REDIS_SCAN_PAGE_SIZE_MIN, REDIS_SCAN_PAGE_SIZE_MAX, REDIS_SCAN_PAGE_SIZE_OPTIONS } from "@/lib/redis/redisKeyPattern"; import { useSettingsStore } from "@/stores/settingsStore"; import { useToast } from "@/composables/useToast"; @@ -1529,8 +1530,9 @@ function switchGbaseProfile(profile: "gbase8a" | "gbase8s") { watch( [() => props.editConfig, open], ([config, isOpen]) => { - if (!isOpen) return; - if (config) { + const syncAction = connectionEditDraftSyncAction(config?.id ?? null, isOpen, editingId.value); + if (syncAction === "preserve") return; + if (syncAction === "hydrate" && config) { clearSavedDatabaseInfo(); const legacyConfig = config as LegacyConnectionConfig; const profile = profileForConfig(config); diff --git a/apps/desktop/src/components/connection/__tests__/connectionEditDraftSync.spec.ts b/apps/desktop/src/components/connection/__tests__/connectionEditDraftSync.spec.ts new file mode 100644 index 0000000000..a007adad0e --- /dev/null +++ b/apps/desktop/src/components/connection/__tests__/connectionEditDraftSync.spec.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; +import { connectionEditDraftSyncAction } from "../connectionEditDraftSync"; + +describe("connectionEditDraftSyncAction", () => { + it("hydrates normal connection edits without reloading an active draft", () => { + expect(connectionEditDraftSyncAction("connection-a", true, null)).toBe("hydrate"); + expect(connectionEditDraftSyncAction("connection-a", true, "connection-a")).toBe("preserve"); + expect(connectionEditDraftSyncAction("connection-b", true, "connection-a")).toBe("hydrate"); + expect(connectionEditDraftSyncAction("connection-a", false, "connection-a")).toBe("preserve"); + expect(connectionEditDraftSyncAction("connection-a", true, null)).toBe("hydrate"); + }); + + it("keeps a copied connection draft when its saved snapshot is refreshed", () => { + const draft = { id: "connection-b", host: "edited.example.test", password: "edited-secret" }; + const savedCopySnapshot = { id: "connection-b", host: "original.example.test", password: "original-secret" }; + + const action = connectionEditDraftSyncAction(savedCopySnapshot.id, true, draft.id); + + expect(action).toBe("preserve"); + expect(draft).toEqual({ id: "connection-b", host: "edited.example.test", password: "edited-secret" }); + }); +}); diff --git a/apps/desktop/src/components/connection/connectionEditDraftSync.ts b/apps/desktop/src/components/connection/connectionEditDraftSync.ts new file mode 100644 index 0000000000..3c78fa8989 --- /dev/null +++ b/apps/desktop/src/components/connection/connectionEditDraftSync.ts @@ -0,0 +1,9 @@ +export type ConnectionEditDraftSyncAction = "hydrate" | "reset" | "preserve"; + +export function connectionEditDraftSyncAction(configId: string | null, isOpen: boolean, activeDraftId: string | null): ConnectionEditDraftSyncAction { + if (!isOpen) return "preserve"; + // Saved snapshots for the active connection must not overwrite its draft, + // while a different id is an explicit request to switch editing targets. + if (configId && configId === activeDraftId) return "preserve"; + return configId ? "hydrate" : "reset"; +}