diff --git a/desktop/src/features/settings/ui/SettingsPanels.tsx b/desktop/src/features/settings/ui/SettingsPanels.tsx index 05e68336..c08520c3 100644 --- a/desktop/src/features/settings/ui/SettingsPanels.tsx +++ b/desktop/src/features/settings/ui/SettingsPanels.tsx @@ -124,7 +124,8 @@ function ThemeSettingsCard() {

Appearance

- Choose a theme for Sprout. Light and dark mode is auto-detected. + Pick the theme Sprout should use. Your selection stays active until + you choose another one.

diff --git a/desktop/src/shared/theme/ThemeProvider.tsx b/desktop/src/shared/theme/ThemeProvider.tsx index 36d5f55c..47b444da 100644 --- a/desktop/src/shared/theme/ThemeProvider.tsx +++ b/desktop/src/shared/theme/ThemeProvider.tsx @@ -10,6 +10,7 @@ import { import { createThemeVars, hexToHsl } from "./adaptive-theme"; import { SYNTAX_THEMES, + isLightTheme, type SyntaxThemeName, extractThemeInfo, loadThemeData, @@ -85,6 +86,12 @@ function applyAccentColor(hex: string) { root.style.setProperty("--sidebar-primary-foreground", fgHsl); } +function applyThemeClass(themeName: SyntaxThemeName) { + const root = document.documentElement; + root.classList.remove("light", "dark"); + root.classList.add(isLightTheme(themeName) ? "light" : "dark"); +} + /** Apply cached CSS vars synchronously to prevent FOUC. */ function applyCachedVars(): string | null { try { @@ -146,7 +153,10 @@ export function ThemeProvider({ // Apply cached vars synchronously before first render const [themeName, setThemeName] = useState(() => { const cached = applyCachedVars(); - return cached ?? readStoredTheme(defaultTheme); + if (cached) return cached; + const storedTheme = readStoredTheme(defaultTheme); + applyThemeClass(storedTheme); + return storedTheme; }); const [isDark, setIsDark] = useState(() => { return document.documentElement.classList.contains("dark"); diff --git a/desktop/tests/e2e/profile.spec.ts b/desktop/tests/e2e/profile.spec.ts index 921e712f..4e501d79 100644 --- a/desktop/tests/e2e/profile.spec.ts +++ b/desktop/tests/e2e/profile.spec.ts @@ -290,7 +290,7 @@ test("opens settings with the keyboard shortcut and updates theme", async ({ await expect(page.getByTestId("settings-nav-appearance")).toBeVisible(); await page.getByTestId("settings-nav-appearance").click(); - // Default theme is catppuccin-macchiato (dark) + // Default theme is houston (dark) await expect .poll(() => page.evaluate(() => document.documentElement.classList.contains("dark")), @@ -306,27 +306,37 @@ test("opens settings with the keyboard shortcut and updates theme", async ({ ) .toBe(true); + await expect + .poll(() => page.evaluate(() => localStorage.getItem("sprout-theme"))) + .toBe("github-light"); + + const primaryBeforeAccent = await page.evaluate(() => + document.documentElement.style.getPropertyValue("--primary").trim(), + ); + expect(primaryBeforeAccent).toBeTruthy(); + + // Accent choice should persist across later theme switches. + await page.getByTestId("accent-color-red").click(); + await expect .poll(() => - page.evaluate(() => document.documentElement.classList.contains("dark")), + page.evaluate(() => localStorage.getItem("sprout-accent-color")), ) - .toBe(false); + .toBe("#ef4444"); - // CSS variables are set on the root element (the real theming mechanism) await expect .poll(() => page.evaluate(() => - document.documentElement.style.getPropertyValue("--background").trim(), + document.documentElement.style.getPropertyValue("--primary").trim(), ), ) - .toBeTruthy(); + .not.toBe(primaryBeforeAccent); - // Theme name persists in localStorage - await expect - .poll(() => page.evaluate(() => localStorage.getItem("sprout-theme"))) - .toBe("github-light"); + const redAccent = await page.evaluate(() => + document.documentElement.style.getPropertyValue("--primary").trim(), + ); + expect(redAccent).toBeTruthy(); - // Switch back to a dark theme — verifies light→dark transition await page.getByTestId("theme-option-dracula").click(); await expect @@ -339,6 +349,14 @@ test("opens settings with the keyboard shortcut and updates theme", async ({ .poll(() => page.evaluate(() => localStorage.getItem("sprout-theme"))) .toBe("dracula"); + await expect + .poll(() => + page.evaluate(() => + document.documentElement.style.getPropertyValue("--primary").trim(), + ), + ) + .toBe(redAccent); + // Close settings with keyboard shortcut await page.keyboard.press( process.platform === "darwin" ? "Meta+," : "Control+,",