feat(desktop): default new installs to dark theme (#53)

Coding workbench, eyes-friendly default. The theme picker still has
white/light/dark/system; this only flips the fallback that applies when:

  - localStorage has no `cc-haha-theme` (fresh install)
  - the server has no persisted user theme (`getUser` returns no
    `theme` field)

Existing users who already set any preference (including 'white') keep
theirs — both the localStorage hydration in uiStore and the fetchAll
merge in settingsStore preserve a stored value over the new default.

Files:
  - desktop/index.html: prepaint <html data-theme> "white" -> "dark"
    (this is the value the page shows for the few ms before
    initializeTheme() runs, so flipping it kills the white flash)
  - desktop/src/stores/uiStore.ts: getStoredTheme() fallback
  - desktop/src/stores/settingsStore.ts: fetchAll serverTheme fallback
  - tests for both stores updated; cycle test pinned to an explicit
    starting point so it does not depend on the default

Tested:
  - bunx vitest src/stores/uiStore.test.ts (3/3)
  - bunx vitest src/stores/settingsStore.test.ts (31/31)
  - bunx vitest src/__tests__/generalSettings.test.tsx (54/54)
  - bunx vitest src/__tests__/diagnosticsSettings.test.tsx + Mermaid +
    DiffViewer (10/10)
  - bun run lint (tsc --noEmit) clean

Confidence: high
Scope-risk: narrow

Co-authored-by: 你的姓名 <you@example.com>
This commit is contained in:
小橙子 2026-06-16 01:39:17 +08:00 committed by GitHub
parent f31b3c39f5
commit de88dd792e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 19 additions and 14 deletions

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="zh-CN" data-theme="white">
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="UTF-8" />
<!--

View File

@ -1008,7 +1008,7 @@ describe('settingsStore theme persistence', () => {
document.documentElement.style.colorScheme = ''
})
it('falls back to the pure white theme when user settings have no theme', async () => {
it('falls back to dark when user settings have no theme', async () => {
vi.doMock('../api/settings', () => ({
settingsApi: {
getUser: vi.fn().mockResolvedValue({}),
@ -1049,10 +1049,10 @@ describe('settingsStore theme persistence', () => {
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')
expect(useSettingsStore.getState().theme).toBe('dark')
expect(useUIStore.getState().theme).toBe('dark')
expect(document.documentElement.getAttribute('data-theme')).toBe('dark')
expect(document.documentElement.style.colorScheme).toBe('dark')
})
it('hydrates the pure white theme from user settings', async () => {

View File

@ -229,7 +229,7 @@ export const useSettingsStore = create<SettingsStore>((set, get) => ({
loadH5AccessSettings(previousH5Access),
loadTraceCaptureSettings(),
])
const serverTheme = isThemeMode(userSettings.theme) ? userSettings.theme : 'white'
const serverTheme = isThemeMode(userSettings.theme) ? userSettings.theme : 'dark'
// 'system' is a desktop-only logical mode that never gets persisted to the
// server (the server rejects it). If the user previously chose 'system' and
// it was stored locally, honour the local value instead of overwriting it

View File

@ -8,16 +8,16 @@ describe('uiStore theme handling', () => {
document.documentElement.style.colorScheme = ''
})
it('defaults new installs to the pure white theme', async () => {
it('defaults new installs to dark', async () => {
const { initializeTheme, useUIStore } = await import('./uiStore')
expect(useUIStore.getState().theme).toBe('white')
expect(useUIStore.getState().theme).toBe('dark')
initializeTheme()
expect(document.documentElement.getAttribute('data-theme')).toBe('white')
expect(document.documentElement.style.colorScheme).toBe('light')
expect(document.documentElement.getAttribute('data-theme')).toBe('dark')
expect(document.documentElement.style.colorScheme).toBe('dark')
})
it('hydrates and applies the pure white theme as a light color scheme', async () => {
it('honours an existing stored preference over the new default (no upgrade clobber)', async () => {
window.localStorage.setItem('cc-haha-theme', 'white')
const { initializeTheme, useUIStore } = await import('./uiStore')
@ -31,7 +31,10 @@ describe('uiStore theme handling', () => {
it('cycles through all theme modes in order', async () => {
const { useUIStore } = await import('./uiStore')
// white (default) → light
// Pin the starting point so this test does not depend on the default.
useUIStore.getState().setTheme('white')
// white → light
useUIStore.getState().toggleTheme()
expect(useUIStore.getState().theme).toBe('light')
expect(document.documentElement.style.colorScheme).toBe('light')

View File

@ -8,7 +8,9 @@ function getStoredTheme(): ThemeMode {
const stored = localStorage.getItem(THEME_STORAGE_KEY)
if (isThemeMode(stored)) return stored
} catch { /* localStorage unavailable */ }
return 'white'
// New installs default to dark — easier on the eyes for a coding workbench.
// Existing users with a stored preference (including 'white') keep theirs.
return 'dark'
}
function getSystemPrefersDark(): boolean {