Skip to content

Commit 2815f85

Browse files
authored
fix(theme): remove stale dark root class in light mode (#1960)
## Summary - fix theme-store bootstrap so the DOM theme name is authoritative on first load - remove stale root/body `dark` classes when the active theme is light - add a regression test covering light theme startup with stale dark classes present ## Root cause The frontend bootstrap path could trust a pre-existing `.dark` class before reconciling against the authoritative DOM theme name. If that class lingered on `<html>` from an earlier client-side mutation, a light theme could still boot into Tailwind dark mode. ## Validation - `pnpm test __test__/store/theme.test.ts` - `pnpm test __test__/components/ThemeSwitcher.test.ts` ## Reference - https://product.unraid.net/p/dark-class-added-to-html-root-element-even-in-light-mode <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Added test coverage for theme initialization to verify correct handling of light theme CSS variables and removal of stale dark mode classes. * **Refactor** * Improved theme store initialization logic to read theme settings from DOM CSS variables, with fallback to legacy initialization when needed. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 26d5f8b commit 2815f85

File tree

2 files changed

+23
-11
lines changed

2 files changed

+23
-11
lines changed

web/__test__/store/theme.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,5 +286,17 @@ describe('Theme Store', () => {
286286

287287
vi.restoreAllMocks();
288288
});
289+
290+
it('should remove stale dark classes when the DOM theme is light', () => {
291+
document.documentElement.style.setProperty('--theme-name', 'white');
292+
originalDocumentElementAddClass.call(document.documentElement.classList, 'dark');
293+
originalAddClassFn.call(document.body.classList, 'dark');
294+
295+
const store = createStore();
296+
297+
expect(document.documentElement.classList.remove).toHaveBeenCalledWith('dark');
298+
expect(document.body.classList.remove).toHaveBeenCalledWith('dark');
299+
expect(store.darkMode).toBe(false);
300+
});
289301
});
290302
});

web/src/store/theme.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ const getCssVar = (name: string): string => {
4848

4949
const readDomThemeName = () => getCssVar('--theme-name');
5050

51+
const isDarkThemeName = (themeName: string) =>
52+
DARK_UI_THEMES.includes(themeName as (typeof DARK_UI_THEMES)[number]);
53+
5154
const syncDarkClass = (method: 'add' | 'remove') => {
5255
if (!isDomAvailable()) return;
5356
document.documentElement.classList[method]('dark');
@@ -98,12 +101,6 @@ export const useThemeStore = defineStore('theme', () => {
98101
const devOverride = ref(false);
99102
const darkMode = ref<boolean>(false);
100103

101-
// Initialize dark mode from CSS variable set by PHP or any pre-applied .dark class
102-
if (isDomAvailable()) {
103-
darkMode.value = isDarkModeActive();
104-
bootstrapDarkClass(darkMode);
105-
}
106-
107104
// Lazy query - only executes when explicitly called
108105
const { load, onResult, onError } = useLazyQuery<GetThemeQuery>(GET_THEME_QUERY, null, {
109106
fetchPolicy: 'cache-and-network',
@@ -208,16 +205,19 @@ export const useThemeStore = defineStore('theme', () => {
208205
watch(
209206
() => theme.value.name,
210207
(themeName) => {
211-
const isDark = DARK_UI_THEMES.includes(themeName as (typeof DARK_UI_THEMES)[number]);
212-
applyDarkClass(isDark, darkMode);
208+
applyDarkClass(isDarkThemeName(themeName), darkMode);
213209
},
214210
{ immediate: false }
215211
);
216212

217213
// Initialize theme from DOM on store creation
218-
const domThemeName = themeName.value;
219-
if (domThemeName && domThemeName !== DEFAULT_THEME.name) {
220-
theme.value.name = domThemeName;
214+
const domThemeName = readDomThemeName();
215+
if (domThemeName) {
216+
setTheme({ name: domThemeName });
217+
applyDarkClass(isDarkThemeName(domThemeName), darkMode);
218+
} else if (isDomAvailable()) {
219+
darkMode.value = isDarkModeActive();
220+
bootstrapDarkClass(darkMode);
221221
}
222222

223223
return {

0 commit comments

Comments
 (0)