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+,",