From d5cf623b0a5784d361fe135f94561ab0f7477369 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Wed, 13 May 2026 16:17:50 -0400 Subject: [PATCH] fix(web): reconcile stale dark class from CSS var Purpose of the change: - Make the web theme bootstrap remove stale dark classes when the server only provides --theme-dark-mode: 0. How behavior was before: - When --theme-name was unavailable, bootstrap only added dark if dark mode was detected. - A stale html/body dark class could remain even while --theme-dark-mode was 0. Why that was a problem: - Light Unraid themes could still render Tailwind dark styles after a stale class survived on the page root. What the new change accomplishes: - Treats the computed dark-mode state as authoritative in the fallback path and reconciles both add and remove cases. - Adds regression coverage for a light CSS variable with stale dark classes. How it works: - Reuses applyDarkClass(isDarkModeActive(), darkMode) when --theme-name is missing so the existing sync path updates html, body, .unapi, and the store state together. --- web/__test__/store/theme.test.ts | 12 ++++++++++++ web/src/store/theme.ts | 9 +-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/web/__test__/store/theme.test.ts b/web/__test__/store/theme.test.ts index f301ac2f2d..286bdfd9c7 100644 --- a/web/__test__/store/theme.test.ts +++ b/web/__test__/store/theme.test.ts @@ -298,5 +298,17 @@ describe('Theme Store', () => { expect(document.body.classList.remove).toHaveBeenCalledWith('dark'); expect(store.darkMode).toBe(false); }); + + it('should remove stale dark classes when only the dark mode CSS variable is light', () => { + document.documentElement.style.setProperty('--theme-dark-mode', '0'); + originalDocumentElementAddClass.call(document.documentElement.classList, 'dark'); + originalAddClassFn.call(document.body.classList, 'dark'); + + const store = createStore(); + + expect(document.documentElement.classList.remove).toHaveBeenCalledWith('dark'); + expect(document.body.classList.remove).toHaveBeenCalledWith('dark'); + expect(store.darkMode).toBe(false); + }); }); }); diff --git a/web/src/store/theme.ts b/web/src/store/theme.ts index cd0f6178ef..e4f3208330 100644 --- a/web/src/store/theme.ts +++ b/web/src/store/theme.ts @@ -68,12 +68,6 @@ const applyDarkClass = (isDark: boolean, darkModeRef?: { value: boolean }) => { } }; -const bootstrapDarkClass = (darkModeRef?: { value: boolean }) => { - if (isDarkModeActive()) { - applyDarkClass(true, darkModeRef); - } -}; - const sanitizeTheme = (data: Partial | null | undefined): Theme | null => { if (!data || typeof data !== 'object') { return null; @@ -216,8 +210,7 @@ export const useThemeStore = defineStore('theme', () => { setTheme({ name: domThemeName }); applyDarkClass(isDarkThemeName(domThemeName), darkMode); } else if (isDomAvailable()) { - darkMode.value = isDarkModeActive(); - bootstrapDarkClass(darkMode); + applyDarkClass(isDarkModeActive(), darkMode); } return {