From 5ed108125263b70c4d9eefb8797f166b61e4a31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Sat, 16 May 2026 00:18:21 +0800 Subject: [PATCH] Default desktop startup to pure white New desktop installs and settings payloads without an explicit theme now hydrate to the pure white workspace. The General settings selector keeps the other two themes available, but presents pure white first so the default and visible ordering agree. Constraint: Existing persisted theme choices must continue to win over the default. Rejected: Remove the warm classic theme | users still need the alternate light appearance. Confidence: high Scope-risk: narrow Directive: Keep the localStorage and user-settings theme fallbacks aligned when changing desktop theme defaults. Tested: cd desktop && bun run test src/stores/uiStore.test.ts src/stores/settingsStore.test.ts src/__tests__/generalSettings.test.tsx Tested: cd desktop && bun run lint Tested: bun run check:desktop Tested: git diff --check Not-tested: Full root bun run verify. --- desktop/index.html | 2 +- .../src/__tests__/generalSettings.test.tsx | 6 +++ desktop/src/pages/Settings.tsx | 2 +- desktop/src/stores/settingsStore.test.ts | 47 +++++++++++++++++++ desktop/src/stores/settingsStore.ts | 2 +- desktop/src/stores/uiStore.test.ts | 19 ++++++-- desktop/src/stores/uiStore.ts | 4 +- desktop/src/types/settings.ts | 2 +- 8 files changed, 73 insertions(+), 11 deletions(-) diff --git a/desktop/index.html b/desktop/index.html index a4064a84..4cf2a5cd 100644 --- a/desktop/index.html +++ b/desktop/index.html @@ -1,5 +1,5 @@ - + diff --git a/desktop/src/__tests__/generalSettings.test.tsx b/desktop/src/__tests__/generalSettings.test.tsx index 7a2d5e1d..3cde1548 100644 --- a/desktop/src/__tests__/generalSettings.test.tsx +++ b/desktop/src/__tests__/generalSettings.test.tsx @@ -240,6 +240,12 @@ describe('Settings > General tab', () => { render() fireEvent.click(screen.getByText('General')) + const pureWhite = screen.getByRole('button', { name: 'Pure White' }) + const warmClassic = screen.getByRole('button', { name: 'Warm Classic' }) + const dark = screen.getByRole('button', { name: 'Dark' }) + + expect((pureWhite.compareDocumentPosition(warmClassic) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true) + expect((warmClassic.compareDocumentPosition(dark) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true) fireEvent.click(screen.getByRole('button', { name: 'Pure White' })) expect(useSettingsStore.getState().setTheme).toHaveBeenCalledWith('white') diff --git a/desktop/src/pages/Settings.tsx b/desktop/src/pages/Settings.tsx index ddbaaf44..1b94b5c8 100644 --- a/desktop/src/pages/Settings.tsx +++ b/desktop/src/pages/Settings.tsx @@ -1459,9 +1459,9 @@ function GeneralSettings() { RESPONSE_LANGUAGES.find(({ value }) => value === responseLanguage)?.label ?? RESPONSE_LANGUAGES[0]!.label const THEMES: Array<{ value: ThemeMode; label: string }> = [ + { value: 'white', label: t('settings.general.appearance.white') }, { value: 'light', label: t('settings.general.appearance.light') }, { value: 'dark', label: t('settings.general.appearance.dark') }, - { value: 'white', label: t('settings.general.appearance.white') }, ] const WEB_SEARCH_MODES: Array<{ value: WebSearchMode; label: string }> = [ diff --git a/desktop/src/stores/settingsStore.test.ts b/desktop/src/stores/settingsStore.test.ts index 1e15d18f..8c45f172 100644 --- a/desktop/src/stores/settingsStore.test.ts +++ b/desktop/src/stores/settingsStore.test.ts @@ -314,6 +314,53 @@ describe('settingsStore theme persistence', () => { document.documentElement.style.colorScheme = '' }) + it('falls back to the pure white theme when user settings have no theme', async () => { + vi.doMock('../api/settings', () => ({ + settingsApi: { + getUser: vi.fn().mockResolvedValue({}), + updateUser: vi.fn(), + getPermissionMode: vi.fn().mockResolvedValue({ mode: 'default' }), + setPermissionMode: vi.fn(), + getCliLauncherStatus: vi.fn(), + }, + })) + vi.doMock('../api/models', () => ({ + modelsApi: { + list: vi.fn().mockResolvedValue({ models: [] }), + getCurrent: vi.fn().mockResolvedValue({ model: null }), + setCurrent: vi.fn(), + getEffort: vi.fn().mockResolvedValue({ level: 'medium' }), + setEffort: vi.fn(), + }, + })) + vi.doMock('../api/h5Access', () => ({ + h5AccessApi: { + get: vi.fn().mockResolvedValue({ + settings: { + enabled: false, + tokenPreview: null, + allowedOrigins: [], + publicBaseUrl: null, + }, + }), + enable: vi.fn(), + disable: vi.fn(), + regenerate: vi.fn(), + update: vi.fn(), + }, + })) + + const { useSettingsStore } = await import('./settingsStore') + const { useUIStore } = await import('./uiStore') + + await useSettingsStore.getState().fetchAll() + + expect(useSettingsStore.getState().theme).toBe('white') + expect(useUIStore.getState().theme).toBe('white') + expect(document.documentElement.getAttribute('data-theme')).toBe('white') + expect(document.documentElement.style.colorScheme).toBe('light') + }) + it('hydrates the pure white theme from user settings', async () => { vi.doMock('../api/settings', () => ({ settingsApi: { diff --git a/desktop/src/stores/settingsStore.ts b/desktop/src/stores/settingsStore.ts index f87d4a64..51e628ab 100644 --- a/desktop/src/stores/settingsStore.ts +++ b/desktop/src/stores/settingsStore.ts @@ -116,7 +116,7 @@ export const useSettingsStore = create((set, get) => ({ settingsApi.getUser(), loadH5AccessSettings(previousH5Access), ]) - const theme = isThemeMode(userSettings.theme) ? userSettings.theme : 'light' + const theme = isThemeMode(userSettings.theme) ? userSettings.theme : 'white' useUIStore.getState().setTheme(theme) set({ permissionMode: mode, diff --git a/desktop/src/stores/uiStore.test.ts b/desktop/src/stores/uiStore.test.ts index 6f0dfd45..ba719d6f 100644 --- a/desktop/src/stores/uiStore.test.ts +++ b/desktop/src/stores/uiStore.test.ts @@ -8,6 +8,15 @@ describe('uiStore theme handling', () => { document.documentElement.style.colorScheme = '' }) + it('defaults new installs to the pure white theme', async () => { + const { initializeTheme, useUIStore } = await import('./uiStore') + + expect(useUIStore.getState().theme).toBe('white') + initializeTheme() + expect(document.documentElement.getAttribute('data-theme')).toBe('white') + expect(document.documentElement.style.colorScheme).toBe('light') + }) + it('hydrates and applies the pure white theme as a light color scheme', async () => { window.localStorage.setItem('cc-haha-theme', 'white') @@ -19,9 +28,13 @@ describe('uiStore theme handling', () => { expect(document.documentElement.style.colorScheme).toBe('light') }) - it('cycles through light, dark, and pure white themes', async () => { + it('cycles through pure white, warm classic, and dark themes', async () => { const { useUIStore } = await import('./uiStore') + useUIStore.getState().toggleTheme() + expect(useUIStore.getState().theme).toBe('light') + expect(document.documentElement.style.colorScheme).toBe('light') + useUIStore.getState().toggleTheme() expect(useUIStore.getState().theme).toBe('dark') expect(document.documentElement.style.colorScheme).toBe('dark') @@ -29,9 +42,5 @@ describe('uiStore theme handling', () => { useUIStore.getState().toggleTheme() expect(useUIStore.getState().theme).toBe('white') expect(document.documentElement.style.colorScheme).toBe('light') - - useUIStore.getState().toggleTheme() - expect(useUIStore.getState().theme).toBe('light') - expect(document.documentElement.style.colorScheme).toBe('light') }) }) diff --git a/desktop/src/stores/uiStore.ts b/desktop/src/stores/uiStore.ts index 5ecc96ca..686f0898 100644 --- a/desktop/src/stores/uiStore.ts +++ b/desktop/src/stores/uiStore.ts @@ -8,7 +8,7 @@ function getStoredTheme(): ThemeMode { const stored = localStorage.getItem(THEME_STORAGE_KEY) if (isThemeMode(stored)) return stored } catch { /* localStorage unavailable */ } - return 'light' + return 'white' } export function applyTheme(theme: ThemeMode) { @@ -89,7 +89,7 @@ export const useUIStore = create((set) => ({ toggleTheme: () => { set((state) => { const currentIndex = THEME_MODES.indexOf(state.theme) - const next = THEME_MODES[(currentIndex + 1) % THEME_MODES.length] ?? 'light' + const next = THEME_MODES[(currentIndex + 1) % THEME_MODES.length] ?? 'white' applyTheme(next) try { localStorage.setItem(THEME_STORAGE_KEY, next) } catch { /* noop */ } return { theme: next } diff --git a/desktop/src/types/settings.ts b/desktop/src/types/settings.ts index 4a5b247a..d5ea3aa8 100644 --- a/desktop/src/types/settings.ts +++ b/desktop/src/types/settings.ts @@ -3,7 +3,7 @@ export type PermissionMode = 'default' | 'acceptEdits' | 'plan' | 'bypassPermissions' | 'dontAsk' export type EffortLevel = 'low' | 'medium' | 'high' | 'max' -export const THEME_MODES = ['light', 'dark', 'white'] as const +export const THEME_MODES = ['white', 'light', 'dark'] as const export type ThemeMode = (typeof THEME_MODES)[number] export function isThemeMode(value: unknown): value is ThemeMode {