diff --git a/apps/desktop/src/lib/dataGrid/dataGridCopyExtractor.ts b/apps/desktop/src/lib/dataGrid/dataGridCopyExtractor.ts index c9cf899d57..f976c48e61 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridCopyExtractor.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridCopyExtractor.ts @@ -29,6 +29,7 @@ export const DATA_GRID_DEFAULT_COPY_PREFERENCES: readonly DataGridCopyPreference ]; export const DATA_GRID_EXTRACTOR_CONTRACT_VERSION = 1 as const; +export const DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION = 1 as const; export const DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS: Record = { raw: { category: "raw" }, diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index 807dc5efb8..e13d894f5b 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -15,6 +15,7 @@ import { transitionRightSidebarPanels, } from "@/stores/settingsStore"; import type { AiConfigItem } from "@/types/ai"; +import { DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION } from "@/lib/dataGrid/dataGridCopyExtractor"; describe("normalizeEditorSettings", () => { it("keeps automatic DDL refresh disabled unless explicitly enabled", () => { @@ -332,6 +333,24 @@ describe("normalizeEditorSettings", () => { expect(configured.json.pretty).toBe(false); }); + it("migrates the legacy NULL clipboard sentinel once", () => { + const legacy = normalizeEditorSettings({ + dataGridExtractorOptions: { + dsv: { ...DEFAULT_EDITOR_SETTINGS.dataGridExtractorOptions.dsv, nullText: "NULL" }, + }, + }); + expect(legacy.dataGridExtractorOptions.dsv.nullText).toBe(""); + expect(legacy.dataGridExtractorOptionsMigrationVersion).toBe(DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION); + + const configured = normalizeEditorSettings({ + dataGridExtractorOptionsMigrationVersion: DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION, + dataGridExtractorOptions: { + dsv: { ...DEFAULT_EDITOR_SETTINGS.dataGridExtractorOptions.dsv, nullText: "NULL" }, + }, + }); + expect(configured.dataGridExtractorOptions.dsv.nullText).toBe("NULL"); + }); + it("defaults retained result runs to tiled tabs and preserves list mode", () => { expect(normalizeEditorSettings({}).resultRunDisplayMode).toBe("tabs"); expect(normalizeEditorSettings({ resultRunDisplayMode: "list" }).resultRunDisplayMode).toBe("list"); diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 6d554806eb..595dabfb70 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -7,7 +7,7 @@ import * as api from "@/lib/backend/api"; import { setDebugLoggingEnabled } from "@/lib/backend/debugLog"; import { safeLocalStorageGet, safeLocalStorageRemove } from "@/lib/backend/safeStorage"; import { type ColumnFormatterConfig, type CustomColumnFormatterConfig, normalizeColumnFormatter, normalizeCustomColumnFormatter, normalizeGlobalDateTimePattern } from "@/lib/dataGrid/columnFormatter"; -import { type DataGridCopyPreference, type DataGridExtractorOptions, DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, normalizeDataGridCopyPreference, normalizeDataGridExtractorOptions } from "@/lib/dataGrid/dataGridCopyExtractor"; +import { type DataGridCopyPreference, type DataGridExtractorOptions, DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION, DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, normalizeDataGridCopyPreference, normalizeDataGridExtractorOptions } from "@/lib/dataGrid/dataGridCopyExtractor"; import { DATA_GRID_TEXT_FILTER_PANEL_HEIGHT_DEFAULT, normalizeDataGridTextFilterPanelHeight } from "@/lib/dataGrid/dataGridTextFilterPanel"; import { DATA_GRID_TYPE_COLOR_SCHEME_AUTO_ID, type DataGridTypeColorScheme, normalizeActiveDataGridTypeColorSchemeId, normalizeDataGridTypeColorSchemes } from "@/lib/dataGrid/dataGridTypeColorScheme"; import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize"; @@ -793,6 +793,7 @@ export interface EditorSettings { dataGridSearchMode: DataGridSearchMode; dataGridCopyExtractor: DataGridCopyPreference; dataGridExtractorOptions: DataGridExtractorOptions; + dataGridExtractorOptionsMigrationVersion: number; resultRunDisplayMode: ResultRunDisplayMode; multiStatementDefaultView: MultiStatementDefaultView; dataGridAutoTransposeSingleRow: boolean; @@ -1027,6 +1028,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { dataGridSearchMode: "filter", dataGridCopyExtractor: "smart", dataGridExtractorOptions: normalizeDataGridExtractorOptions(DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS), + dataGridExtractorOptionsMigrationVersion: DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION, resultRunDisplayMode: "tabs", multiStatementDefaultView: "result", dataGridAutoTransposeSingleRow: false, @@ -1369,6 +1371,10 @@ export function normalizeEditorSettings(settings: Partial, exist const hasCurrentExecuteModeDefault = executeModeDefaultVersion === savedExecuteModeDefaultVersion; // The active id can only be validated once the scheme list it points into is known. const dataGridTypeColorSchemes = normalizeDataGridTypeColorSchemes(settings.dataGridTypeColorSchemes); + const savedExtractorMigrationVersion = settings.dataGridExtractorOptionsMigrationVersion; + const normalizedExtractorOptions = normalizeDataGridExtractorOptions(settings.dataGridExtractorOptions); + const isLegacyExtractorOptions = typeof savedExtractorMigrationVersion !== "number" || savedExtractorMigrationVersion < DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION; + const dataGridExtractorOptions = isLegacyExtractorOptions && normalizedExtractorOptions.dsv.nullText === "NULL" ? { ...normalizedExtractorOptions, dsv: { ...normalizedExtractorOptions.dsv, nullText: "" } } : normalizedExtractorOptions; return { fontFamily: normalizeFontFamily(settings.fontFamily, DEFAULT_EDITOR_SETTINGS.fontFamily), fontSize: settings.fontSize ?? DEFAULT_EDITOR_SETTINGS.fontSize, @@ -1475,7 +1481,8 @@ export function normalizeEditorSettings(settings: Partial, exist dataGridRenderMode: normalizeDataGridRenderMode(settings.dataGridRenderMode), dataGridSearchMode: normalizeDataGridSearchMode(settings.dataGridSearchMode), dataGridCopyExtractor: normalizeDataGridCopyPreference(settings.dataGridCopyExtractor), - dataGridExtractorOptions: normalizeDataGridExtractorOptions(settings.dataGridExtractorOptions), + dataGridExtractorOptions, + dataGridExtractorOptionsMigrationVersion: DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION, resultRunDisplayMode: normalizeResultRunDisplayMode(settings.resultRunDisplayMode), multiStatementDefaultView: normalizeMultiStatementDefaultView(settings.multiStatementDefaultView), dataGridAutoTransposeSingleRow: settings.dataGridAutoTransposeSingleRow === true, @@ -1757,8 +1764,10 @@ export const useSettingsStore = defineStore("settings", () => { editorSettings.value = normalized; const needsExecuteModeDefaultMigration = typeof savedSettings.executeModeDefaultVersion !== "number" || savedSettings.executeModeDefaultVersion < EXECUTE_MODE_CURRENT_DEFAULT_VERSION; const needsTabNavigationShortcutMigration = needsTabNavigationHistoryShortcutMigration(savedSettings.shortcuts); + const savedNullText = (savedSettings.dataGridExtractorOptions as Partial | undefined)?.dsv?.nullText; + const needsDataGridExtractorOptionsMigration = (typeof savedSettings.dataGridExtractorOptionsMigrationVersion !== "number" || savedSettings.dataGridExtractorOptionsMigrationVersion < DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION) && savedNullText === "NULL"; const savedUpdateDownloadSource = (saved as { updateDownloadSource?: unknown }).updateDownloadSource; - if (savedUpdateDownloadSource === "atomgit" || needsExecuteModeDefaultMigration || needsTabNavigationShortcutMigration || needsSidebarBrowseObjectsMigration) { + if (savedUpdateDownloadSource === "atomgit" || needsExecuteModeDefaultMigration || needsTabNavigationShortcutMigration || needsSidebarBrowseObjectsMigration || needsDataGridExtractorOptionsMigration) { // Persist one-time migrations so removed or unsafe defaults cannot reappear. await enqueueEditorSettingsSave().catch(() => {}); } @@ -2207,6 +2216,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.dataGridSearchMode !== undefined) editorSettings.value.dataGridSearchMode = normalizeDataGridSearchMode(partial.dataGridSearchMode); if (partial.dataGridCopyExtractor !== undefined) editorSettings.value.dataGridCopyExtractor = normalizeDataGridCopyPreference(partial.dataGridCopyExtractor); if (partial.dataGridExtractorOptions !== undefined) editorSettings.value.dataGridExtractorOptions = normalizeDataGridExtractorOptions(partial.dataGridExtractorOptions); + if (partial.dataGridExtractorOptionsMigrationVersion !== undefined) editorSettings.value.dataGridExtractorOptionsMigrationVersion = DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION; if (partial.resultRunDisplayMode !== undefined) editorSettings.value.resultRunDisplayMode = normalizeResultRunDisplayMode(partial.resultRunDisplayMode); if (partial.multiStatementDefaultView !== undefined) editorSettings.value.multiStatementDefaultView = normalizeMultiStatementDefaultView(partial.multiStatementDefaultView); if (partial.dataGridAutoTransposeSingleRow !== undefined) editorSettings.value.dataGridAutoTransposeSingleRow = partial.dataGridAutoTransposeSingleRow === true;