From 04ab5ec96e77d3012fcb0a2df8f7a6303c88dfd7 Mon Sep 17 00:00:00 2001 From: zipg Date: Tue, 8 Sep 2026 11:56:18 +0800 Subject: [PATCH] fix(settings): preserve legacy browse-objects behavior on activation --- .../stores/__tests__/settingsStore.spec.ts | 4 ++ apps/desktop/src/stores/settingsStore.ts | 21 ++++++-- packages/app-tests/settingsStore.test.ts | 49 +++++++++++++++++-- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index fe90edd679..807dc5efb8 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -5,6 +5,7 @@ import { AI_PROVIDER_PRESETS, DEFAULT_EDITOR_SETTINGS, EXECUTE_MODE_CURRENT_DEFAULT_VERSION, + SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, enforceRightSidebarPanelExclusivity, normalizeAiConfig, normalizeDesktopSettings, @@ -1156,6 +1157,7 @@ describe("settingsStore editor settings persistence", () => { const loadEditorSettings = vi.fn().mockResolvedValue({ ignoredUpdateVersion: "", executeModeDefaultVersion: EXECUTE_MODE_CURRENT_DEFAULT_VERSION, + sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, }); const saveEditorSettings = vi.fn().mockRejectedValueOnce(new Error("save failed")).mockResolvedValueOnce(undefined); vi.doMock("@/lib/backend/api", () => ({ loadEditorSettings, saveEditorSettings })); @@ -1202,6 +1204,7 @@ describe("settingsStore editor settings persistence", () => { const loadEditorSettings = vi.fn().mockResolvedValue({ ignoredUpdateVersion: "", executeModeDefaultVersion: EXECUTE_MODE_CURRENT_DEFAULT_VERSION, + sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, }); const saveEditorSettings = vi.fn().mockImplementationOnce( () => @@ -1237,6 +1240,7 @@ describe("settingsStore editor settings persistence", () => { ignoredUpdateVersion: "", theme: "system", executeModeDefaultVersion: EXECUTE_MODE_CURRENT_DEFAULT_VERSION, + sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, }); const saveEditorSettings = vi.fn().mockImplementationOnce( () => diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 96eeb877d3..6d554806eb 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -823,6 +823,7 @@ export interface EditorSettings { sidebarGlobalSearchLocal: boolean; autoSelectActiveSidebarNode: boolean; sidebarBrowseObjectsOnDatabaseActivation: boolean; + sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: number; openTabsRestoreMode: OpenTabsRestoreMode; disconnectTabHandlingMode: DisconnectTabHandlingMode; dataTabReuseMode: DataTabReuseMode; @@ -951,6 +952,7 @@ export const EDITOR_THEMES: { const EDITOR_THEME_VALUES = new Set(EDITOR_THEMES.map((theme) => theme.value)); export const EXECUTE_MODE_CURRENT_DEFAULT_VERSION = 1; +export const SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION = 1; export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { fontFamily: "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace", @@ -1055,6 +1057,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { sidebarGlobalSearchLocal: false, autoSelectActiveSidebarNode: false, sidebarBrowseObjectsOnDatabaseActivation: false, + sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, openTabsRestoreMode: "all", disconnectTabHandlingMode: "close-tabs", dataTabReuseMode: DEFAULT_DATA_TAB_REUSE_MODE, @@ -1359,6 +1362,7 @@ function normalizeTableInfoTab(value: unknown): TableInfoTab { export function normalizeEditorSettings(settings: Partial, existing?: EditorSettings): EditorSettings { const legacyTimeoutSettings = settings as Partial & { queryTimeoutSecs?: unknown; queryTimeoutInheritanceMigrationVersion?: unknown }; + const legacySidebarOpenDatabaseOnSingleClick = (settings as Partial & { sidebarOpenDatabaseOnSingleClick?: unknown }).sidebarOpenDatabaseOnSingleClick; const sqlSemanticDiagnosticsMode = normalizeSqlSemanticDiagnosticsMode(settings.sqlSemanticDiagnosticsMode, settings.sqlSemanticDiagnosticsEnabled); const savedExecuteModeDefaultVersion = settings.executeModeDefaultVersion; const executeModeDefaultVersion = typeof savedExecuteModeDefaultVersion === "number" && savedExecuteModeDefaultVersion >= EXECUTE_MODE_CURRENT_DEFAULT_VERSION ? savedExecuteModeDefaultVersion : EXECUTE_MODE_CURRENT_DEFAULT_VERSION; @@ -1504,9 +1508,14 @@ export function normalizeEditorSettings(settings: Partial, exist sidebarBrowseObjectsOnDatabaseActivation: typeof settings.sidebarBrowseObjectsOnDatabaseActivation === "boolean" ? settings.sidebarBrowseObjectsOnDatabaseActivation - : typeof (settings as Partial & { sidebarOpenDatabaseOnSingleClick?: unknown }).sidebarOpenDatabaseOnSingleClick === "boolean" - ? (settings as Partial & { sidebarOpenDatabaseOnSingleClick: boolean }).sidebarOpenDatabaseOnSingleClick + : typeof legacySidebarOpenDatabaseOnSingleClick === "boolean" + ? // The legacy false value only disabled single-click browsing; double-click still opened the browser. + true : DEFAULT_EDITOR_SETTINGS.sidebarBrowseObjectsOnDatabaseActivation, + sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: + typeof settings.sidebarBrowseObjectsOnDatabaseActivationMigrationVersion === "number" && settings.sidebarBrowseObjectsOnDatabaseActivationMigrationVersion >= SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION + ? Math.floor(settings.sidebarBrowseObjectsOnDatabaseActivationMigrationVersion) + : SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, openTabsRestoreMode: normalizeOpenTabsRestoreMode( (settings as Partial).openTabsRestoreMode, ( @@ -1739,11 +1748,17 @@ export const useSettingsStore = defineStore("settings", () => { if (saved && typeof saved === "object" && !Array.isArray(saved)) { const savedSettings = saved as Partial; const normalized = normalizeEditorSettings(savedSettings); + const needsSidebarBrowseObjectsMigration = typeof savedSettings.sidebarBrowseObjectsOnDatabaseActivationMigrationVersion !== "number" || savedSettings.sidebarBrowseObjectsOnDatabaseActivationMigrationVersion < SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION; + if (needsSidebarBrowseObjectsMigration) { + // Before this migration, a false value still preserved double-click browsing. + normalized.sidebarBrowseObjectsOnDatabaseActivation = true; + normalized.sidebarBrowseObjectsOnDatabaseActivationMigrationVersion = SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION; + } editorSettings.value = normalized; const needsExecuteModeDefaultMigration = typeof savedSettings.executeModeDefaultVersion !== "number" || savedSettings.executeModeDefaultVersion < EXECUTE_MODE_CURRENT_DEFAULT_VERSION; const needsTabNavigationShortcutMigration = needsTabNavigationHistoryShortcutMigration(savedSettings.shortcuts); const savedUpdateDownloadSource = (saved as { updateDownloadSource?: unknown }).updateDownloadSource; - if (savedUpdateDownloadSource === "atomgit" || needsExecuteModeDefaultMigration || needsTabNavigationShortcutMigration) { + if (savedUpdateDownloadSource === "atomgit" || needsExecuteModeDefaultMigration || needsTabNavigationShortcutMigration || needsSidebarBrowseObjectsMigration) { // Persist one-time migrations so removed or unsafe defaults cannot reappear. await enqueueEditorSettingsSave().catch(() => {}); } diff --git a/packages/app-tests/settingsStore.test.ts b/packages/app-tests/settingsStore.test.ts index 2ba99b19ba..8e820f50c1 100644 --- a/packages/app-tests/settingsStore.test.ts +++ b/packages/app-tests/settingsStore.test.ts @@ -6,7 +6,18 @@ import { DEFAULT_SQL_FORMATTER_SETTINGS } from "../../apps/desktop/src/lib/sql/s import { DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS } from "../../apps/desktop/src/lib/table/tableColumnTemplates.ts"; import { DEFAULT_DATA_GRID_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY, SYSTEM_UI_FONT_FAMILY } from "../../apps/desktop/src/lib/app/appFonts.ts"; import { tableOpenPageLimit } from "../../apps/desktop/src/lib/table/tableOpenPageLimit.ts"; -import { AI_PROVIDER_PARTNER_PRESETS, AI_PROVIDER_PRESETS, DEFAULT_EDITOR_SETTINGS, EXECUTE_MODE_CURRENT_DEFAULT_VERSION, getAiProviderPreset, getAiProviderPresetId, normalizeAiConfig, normalizeEditorSettings, useSettingsStore } from "../../apps/desktop/src/stores/settingsStore.ts"; +import { + AI_PROVIDER_PARTNER_PRESETS, + AI_PROVIDER_PRESETS, + DEFAULT_EDITOR_SETTINGS, + EXECUTE_MODE_CURRENT_DEFAULT_VERSION, + SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, + getAiProviderPreset, + getAiProviderPresetId, + normalizeAiConfig, + normalizeEditorSettings, + useSettingsStore, +} from "../../apps/desktop/src/stores/settingsStore.ts"; import { DEFAULT_SHORTCUT_SETTINGS, tabNavigationHistoryDefaultShortcut, type ShortcutSettings } from "../../apps/desktop/src/lib/editor/shortcutRegistry.ts"; const saveEditorSettingsMock = vi.hoisted(() => vi.fn()); @@ -506,13 +517,45 @@ test("defaults sidebar activation to single click", () => { assert.equal(normalizeEditorSettings({}).sidebarActivation, "single"); }); -test("defaults database activation browsing to off and migrates the previous single-click setting", () => { +test("preserves object browsing for legacy sidebar settings", () => { assert.equal(DEFAULT_EDITOR_SETTINGS.sidebarBrowseObjectsOnDatabaseActivation, false); + assert.equal(DEFAULT_EDITOR_SETTINGS.sidebarBrowseObjectsOnDatabaseActivationMigrationVersion, SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION); assert.equal(normalizeEditorSettings({}).sidebarBrowseObjectsOnDatabaseActivation, false); assert.equal(normalizeEditorSettings({ sidebarBrowseObjectsOnDatabaseActivation: true }).sidebarBrowseObjectsOnDatabaseActivation, true); + assert.equal(normalizeEditorSettings({ sidebarBrowseObjectsOnDatabaseActivation: false }).sidebarBrowseObjectsOnDatabaseActivation, false); assert.equal(normalizeEditorSettings({ sidebarBrowseObjectsOnDatabaseActivation: "yes" as any }).sidebarBrowseObjectsOnDatabaseActivation, false); assert.equal(normalizeEditorSettings({ sidebarOpenDatabaseOnSingleClick: true } as any).sidebarBrowseObjectsOnDatabaseActivation, true); - assert.equal(normalizeEditorSettings({ sidebarOpenDatabaseOnSingleClick: false } as any).sidebarBrowseObjectsOnDatabaseActivation, false); + assert.equal(normalizeEditorSettings({ sidebarOpenDatabaseOnSingleClick: false } as any).sidebarBrowseObjectsOnDatabaseActivation, true); + assert.equal(normalizeEditorSettings({ sidebarBrowseObjectsOnDatabaseActivation: false, sidebarOpenDatabaseOnSingleClick: true } as any).sidebarBrowseObjectsOnDatabaseActivation, false); +}); + +test("migrates existing settings once and preserves a later explicit opt-out", async () => { + await withMockLocalStorage( + { + "dbx-app-state:editor_settings": JSON.stringify({ sidebarBrowseObjectsOnDatabaseActivation: false }), + }, + async () => { + setActivePinia(createPinia()); + const migratedStore = useSettingsStore(); + await migratedStore.initEditorSettings(); + + assert.equal(migratedStore.editorSettings.sidebarBrowseObjectsOnDatabaseActivation, true); + assert.equal(migratedStore.editorSettings.sidebarBrowseObjectsOnDatabaseActivationMigrationVersion, SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION); + await vi.waitFor(() => { + const saved = JSON.parse(localStorage.getItem("dbx-app-state:editor_settings") || "{}") as Record; + assert.equal(saved.sidebarBrowseObjectsOnDatabaseActivation, true); + assert.equal(saved.sidebarBrowseObjectsOnDatabaseActivationMigrationVersion, SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION); + }); + + migratedStore.updateEditorSettings({ sidebarBrowseObjectsOnDatabaseActivation: false }); + await vi.waitFor(() => assert.equal(migratedStore.editorSettings.sidebarBrowseObjectsOnDatabaseActivation, false)); + + setActivePinia(createPinia()); + const reloadedStore = useSettingsStore(); + await reloadedStore.initEditorSettings(); + assert.equal(reloadedStore.editorSettings.sidebarBrowseObjectsOnDatabaseActivation, false); + }, + ); }); test("defaults active tab sidebar selection to off", () => {