diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index e8170d6821..71ea299091 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -1450,7 +1450,7 @@ async function updateAllAvailable() { } } const hasSqlFileConnections = computed(() => connectionStore.connections.some((c) => supportsSqlFileExecution(c.db_type))); -const welcomePageMode = computed(() => (settingsStore.isEditorSettingsLoaded ? settingsStore.editorSettings.welcomePageMode : "intro")); +const welcomePageMode = computed(() => (settingsStore.isEditorSettingsLoaded ? settingsStore.editorSettings.welcomePageMode : "workspace")); const queryEditorDdlDatabaseType = computed(() => { if (!queryEditorDdlTarget.value?.connectionId) return undefined; return effectiveDatabaseTypeForConnection(connectionStore.getConfig(queryEditorDdlTarget.value.connectionId)); diff --git a/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts b/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts index aaa2ee3791..081091ce73 100644 --- a/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts +++ b/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts @@ -73,6 +73,7 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => { it("includes the privacy-safe welcome page mode", () => { expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("welcomePageMode"); + expect(editorSettingsDraftFromSettings(makeSettings({ welcomePageMode: "intro" })).welcomePageMode).toBe("intro"); expect(editorSettingsDraftFromSettings(makeSettings({ welcomePageMode: "workspace" })).welcomePageMode).toBe("workspace"); }); diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index ba1c802d7a..8262ed5f96 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -18,6 +18,7 @@ import { normalizeMcpGlobalPolicy, type RightSidebarPanelState, transitionRightSidebarPanels, + WELCOME_PAGE_DEFAULT_VERSION, } from "@/stores/settingsStore"; import type { AiConfigItem } from "@/types/ai"; import { DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION } from "@/lib/dataGrid/dataGridCopyExtractor"; @@ -31,6 +32,16 @@ describe("normalizeEditorSettings", () => { expect(normalizeEditorSettings({ resultTabPreferComments: "false" } as any).resultTabPreferComments).toBe(true); }); + it("defaults the welcome page to the workspace overview and honors an explicitly saved intro mode", () => { + expect(DEFAULT_EDITOR_SETTINGS.welcomePageMode).toBe("workspace"); + expect(normalizeEditorSettings({}).welcomePageMode).toBe("workspace"); + expect(normalizeEditorSettings({ welcomePageMode: "bogus", welcomePageModeDefaultVersion: WELCOME_PAGE_DEFAULT_VERSION }).welcomePageMode).toBe("workspace"); + // A persisted intro from the intro-default builds carries no version marker and migrates back. + expect(normalizeEditorSettings({ welcomePageMode: "intro" }).welcomePageMode).toBe("workspace"); + expect(normalizeEditorSettings({ welcomePageMode: "intro", welcomePageModeDefaultVersion: WELCOME_PAGE_DEFAULT_VERSION }).welcomePageMode).toBe("intro"); + expect(normalizeEditorSettings({ welcomePageMode: "workspace", welcomePageModeDefaultVersion: WELCOME_PAGE_DEFAULT_VERSION }).welcomePageMode).toBe("workspace"); + }); + it("defaults and sanitizes AI conversation typography independently", () => { expect(normalizeEditorSettings({})).toMatchObject({ aiFontFamily: "", aiFontSize: 12 }); expect( @@ -1327,6 +1338,31 @@ describe("settingsStore persisted settings initialization", () => { expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ dataGridKeepFilterEditorExpanded: false })); }); + it("resets a persisted intro welcome page from the intro-default builds and keeps later explicit choices", async () => { + const loadEditorSettings = vi.fn().mockResolvedValueOnce({ + welcomePageMode: "intro", + executeModeDefaultVersion: EXECUTE_MODE_CURRENT_DEFAULT_VERSION, + sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, + dataGridExtractorOptionsMigrationVersion: DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION, + }); + const saveEditorSettings = vi.fn().mockResolvedValue(undefined); + vi.doMock("@/lib/backend/api", () => ({ loadEditorSettings, saveEditorSettings })); + + const { useSettingsStore } = await import("@/stores/settingsStore"); + const store = useSettingsStore(); + await store.initEditorSettings(); + + expect(store.editorSettings.welcomePageMode).toBe("workspace"); + expect(store.editorSettings.welcomePageModeDefaultVersion).toBe(WELCOME_PAGE_DEFAULT_VERSION); + await vi.waitFor(() => expect(saveEditorSettings).toHaveBeenCalledOnce()); + expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ welcomePageMode: "workspace", welcomePageModeDefaultVersion: WELCOME_PAGE_DEFAULT_VERSION })); + + store.updateEditorSettings({ welcomePageMode: "intro" }); + expect(store.editorSettings.welcomePageMode).toBe("intro"); + await vi.waitFor(() => expect(saveEditorSettings).toHaveBeenCalledTimes(2)); + expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ welcomePageMode: "intro", welcomePageModeDefaultVersion: WELCOME_PAGE_DEFAULT_VERSION })); + }); + it("persists table completion schema qualification updates", async () => { const loadEditorSettings = vi.fn().mockResolvedValue({}); const saveEditorSettings = vi.fn().mockResolvedValue(undefined); @@ -1696,6 +1732,7 @@ describe("settingsStore editor settings persistence", () => { ignoredUpdateVersion: "", executeModeDefaultVersion: EXECUTE_MODE_CURRENT_DEFAULT_VERSION, sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, + welcomePageModeDefaultVersion: WELCOME_PAGE_DEFAULT_VERSION, }); const saveEditorSettings = vi.fn().mockRejectedValueOnce(new Error("save failed")).mockResolvedValueOnce(undefined); vi.doMock("@/lib/backend/api", () => ({ loadEditorSettings, saveEditorSettings })); @@ -1743,6 +1780,7 @@ describe("settingsStore editor settings persistence", () => { ignoredUpdateVersion: "", executeModeDefaultVersion: EXECUTE_MODE_CURRENT_DEFAULT_VERSION, sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, + welcomePageModeDefaultVersion: WELCOME_PAGE_DEFAULT_VERSION, }); const saveEditorSettings = vi.fn().mockImplementationOnce( () => @@ -1779,6 +1817,7 @@ describe("settingsStore editor settings persistence", () => { theme: "system", executeModeDefaultVersion: EXECUTE_MODE_CURRENT_DEFAULT_VERSION, sidebarBrowseObjectsOnDatabaseActivationMigrationVersion: SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION, + welcomePageModeDefaultVersion: WELCOME_PAGE_DEFAULT_VERSION, }); const saveEditorSettings = vi.fn().mockImplementationOnce( () => diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 32a8b39c78..754b511f71 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -906,6 +906,7 @@ export interface EditorSettings { appCloseUnsavedTabsMode: AppCloseUnsavedTabsMode; savedSqlOpenTargetMode: SavedSqlOpenTargetMode; welcomePageMode: WelcomePageMode; + welcomePageModeDefaultVersion: number; compactTabTitle: boolean; tabLayout: TabLayoutMode; tabPlacement: TabPlacement; @@ -1179,6 +1180,9 @@ const EDITOR_THEME_VALUES = new Set(EDITOR_THEMES.map((theme) => th export const EXECUTE_MODE_CURRENT_DEFAULT_VERSION = 1; export const SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION = 1; +// v1: the welcome page default moved from "intro" back to "workspace"; persisted +// blobs from the intro-default builds lack this marker and are migrated. +export const WELCOME_PAGE_DEFAULT_VERSION = 1; export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { fontFamily: DEFAULT_MONO_FONT_FAMILY, @@ -1229,7 +1233,8 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { confirmUnsavedSqlClose: true, appCloseUnsavedTabsMode: "keep-drafts", savedSqlOpenTargetMode: "saved", - welcomePageMode: "intro", + welcomePageMode: "workspace", + welcomePageModeDefaultVersion: WELCOME_PAGE_DEFAULT_VERSION, compactTabTitle: false, tabLayout: "scroll", tabPlacement: "top", @@ -1722,6 +1727,9 @@ export function normalizeEditorSettings(settings: Partial, exist const savedExecuteModeDefaultVersion = settings.executeModeDefaultVersion; const executeModeDefaultVersion = typeof savedExecuteModeDefaultVersion === "number" && savedExecuteModeDefaultVersion >= EXECUTE_MODE_CURRENT_DEFAULT_VERSION ? savedExecuteModeDefaultVersion : EXECUTE_MODE_CURRENT_DEFAULT_VERSION; const hasCurrentExecuteModeDefault = executeModeDefaultVersion === savedExecuteModeDefaultVersion; + const savedWelcomePageDefaultVersion = settings.welcomePageModeDefaultVersion; + const welcomePageModeDefaultVersion = typeof savedWelcomePageDefaultVersion === "number" && savedWelcomePageDefaultVersion >= WELCOME_PAGE_DEFAULT_VERSION ? savedWelcomePageDefaultVersion : WELCOME_PAGE_DEFAULT_VERSION; + const hasCurrentWelcomePageDefault = welcomePageModeDefaultVersion === savedWelcomePageDefaultVersion; // 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; @@ -1825,7 +1833,8 @@ export function normalizeEditorSettings(settings: Partial, exist confirmUnsavedSqlClose: settings.confirmUnsavedSqlClose ?? DEFAULT_EDITOR_SETTINGS.confirmUnsavedSqlClose, appCloseUnsavedTabsMode: normalizeAppCloseUnsavedTabsMode(settings.appCloseUnsavedTabsMode), savedSqlOpenTargetMode: settings.savedSqlOpenTargetMode === "current" ? "current" : DEFAULT_EDITOR_SETTINGS.savedSqlOpenTargetMode, - welcomePageMode: settings.welcomePageMode === "workspace" ? "workspace" : DEFAULT_EDITOR_SETTINGS.welcomePageMode, + welcomePageMode: hasCurrentWelcomePageDefault && (settings.welcomePageMode === "intro" || settings.welcomePageMode === "workspace") ? settings.welcomePageMode : DEFAULT_EDITOR_SETTINGS.welcomePageMode, + welcomePageModeDefaultVersion, compactTabTitle: settings.compactTabTitle ?? DEFAULT_EDITOR_SETTINGS.compactTabTitle, tabLayout: normalizeTabLayout(settings.tabLayout), tabPlacement: normalizeTabPlacement(settings.tabPlacement), @@ -2231,11 +2240,12 @@ export const useSettingsStore = defineStore("settings", () => { query: typeof savedSettings.globalQueryTimeoutSecs === "number" || typeof (savedSettings as { queryTimeoutSecs?: unknown }).queryTimeoutSecs === "number", }; const needsExecuteModeDefaultMigration = typeof savedSettings.executeModeDefaultVersion !== "number" || savedSettings.executeModeDefaultVersion < EXECUTE_MODE_CURRENT_DEFAULT_VERSION; + const needsWelcomePageDefaultMigration = typeof savedSettings.welcomePageModeDefaultVersion !== "number" || savedSettings.welcomePageModeDefaultVersion < WELCOME_PAGE_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 || needsDataGridExtractorOptionsMigration) { + if (savedUpdateDownloadSource === "atomgit" || needsExecuteModeDefaultMigration || needsWelcomePageDefaultMigration || needsTabNavigationShortcutMigration || needsSidebarBrowseObjectsMigration || needsDataGridExtractorOptionsMigration) { // Persist one-time migrations so removed or unsafe defaults cannot reappear. await enqueueEditorSettingsSave().catch(() => {}); } @@ -2678,7 +2688,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.confirmUnsavedSqlClose !== undefined) editorSettings.value.confirmUnsavedSqlClose = partial.confirmUnsavedSqlClose; if (partial.appCloseUnsavedTabsMode !== undefined) editorSettings.value.appCloseUnsavedTabsMode = normalizeAppCloseUnsavedTabsMode(partial.appCloseUnsavedTabsMode); if (partial.savedSqlOpenTargetMode !== undefined) editorSettings.value.savedSqlOpenTargetMode = partial.savedSqlOpenTargetMode === "current" ? "current" : "saved"; - if (partial.welcomePageMode !== undefined) editorSettings.value.welcomePageMode = partial.welcomePageMode === "workspace" ? "workspace" : DEFAULT_EDITOR_SETTINGS.welcomePageMode; + if (partial.welcomePageMode !== undefined) editorSettings.value.welcomePageMode = partial.welcomePageMode === "intro" || partial.welcomePageMode === "workspace" ? partial.welcomePageMode : DEFAULT_EDITOR_SETTINGS.welcomePageMode; if (partial.compactTabTitle !== undefined) editorSettings.value.compactTabTitle = partial.compactTabTitle; if (partial.tabLayout !== undefined) editorSettings.value.tabLayout = normalizeTabLayout(partial.tabLayout); if (partial.tabPlacement !== undefined) editorSettings.value.tabPlacement = normalizeTabPlacement(partial.tabPlacement); diff --git a/docs/home-page-privacy.md b/docs/home-page-privacy.md index 0a86706c3a..83d6d819e7 100644 --- a/docs/home-page-privacy.md +++ b/docs/home-page-privacy.md @@ -4,20 +4,24 @@ DBX 在没有打开查询标签时,可以展示产品介绍或工作区概览 ## 两种模式 -- **DBX 介绍(默认)**:展示产品说明、连接管理、SQL 编辑、数据浏览与传输、AI / MCP 能力,以及新建连接、导入连接等固定入口。不展示个人连接名称、地址、数量、已连接状态、数据库类型统计或 SQL 历史;查询入口继续由现有工具栏提供。 -- **工作区概览**:保留原有连接统计、快速连接和保存的 SQL 历史,适合希望快速回到日常工作的用户。 +- **工作区概览(默认)**:保留原有连接统计、快速连接和保存的 SQL 历史,适合希望快速回到日常工作的用户。 +- **DBX 介绍**:展示产品说明、连接管理、SQL 编辑、数据浏览与传输、AI / MCP 能力,以及新建连接、导入连接等固定入口。不展示个人连接名称、地址、数量、已连接状态、数据库类型统计或 SQL 历史;查询入口继续由现有工具栏提供。适合在展示、录屏或共用屏幕时不想露出个人数据的用户主动开启。 -介绍页采用主题颜色、留白和响应式布局,兼容深浅主题。首页设置支持设置搜索、配置导入导出和恢复默认。模式值为 `welcomePageMode: "intro" | "workspace"`,缺失或无效配置回退为介绍页,因此升级用户与新用户都采用相同的默认行为。 +介绍页采用主题颜色、留白和响应式布局,兼容深浅主题。首页设置支持设置搜索、配置导入导出和恢复默认。模式值为 `welcomePageMode: "intro" | "workspace"`,缺失或无效配置回退为工作区概览。 + +### 默认值迁移 + +介绍页曾在 v0.6.33 短暂作为默认值。当前版本通过 `welcomePageModeDefaultVersion` 标记把默认值迁回工作区概览:没有该标记的历史设置(包括 v0.6.33 期间被整体快照写入的 `intro`)在启动时一次性重置为工作区概览并落盘;标记为当前版本后,用户显式选择的介绍页会跨重启保留。 ## 实现与边界 -`App.vue` 在设置加载完成前保持介绍模式;介绍模式下首页统计、快速连接和 SQL 历史计算直接返回零值或空列表,不读取或排序这些个人数据。`WelcomeScreen.vue` 默认使用介绍模式,个人数据 props 可省略,介绍分支与工作区分支分别渲染。介绍页的按钮布局也不依赖是否已连接数据库。 +`App.vue` 在设置加载完成前保持工作区模式;介绍模式下首页统计、快速连接和 SQL 历史计算直接返回零值或空列表,不读取或排序这些个人数据。`WelcomeScreen.vue` 个人数据 props 可省略,介绍分支与工作区分支分别渲染。介绍页的按钮布局也不依赖是否已连接数据库。 该选项只控制**中央空白首页**,不隐藏连接侧栏、已打开的标签、工具栏或用户主动打开的历史窗口,也不删除连接与历史数据。展示或录屏前如需隐藏整个工作区的个人信息,仍需检查这些区域;这项设置不是全局隐私模式。 ## 验收重点 -1. 无配置、旧配置和无效值均显示介绍页;保存工作区模式后重启恢复该选择。 +1. 无配置、旧配置和无效值均显示工作区概览;保存介绍页模式后重启恢复该选择;v0.6.33 存量的 `intro`(无版本标记)首次启动被迁回工作区概览并持久化。 2. 介绍页不出现连接统计、名称、地址和 SQL 历史,连接或历史数据变化不改变介绍页内容。 3. 切换到工作区概览恢复原有统计与操作,设置取消不保存,应用后立即生效。 4. 外观恢复默认、全局恢复默认、导入导出与设置搜索正确覆盖该选项。