import { useEffect, useRef } from 'react'; import { useTheme } from '@/components/ui/ThemeProvider'; import { themeFromLegacyColorMode, isKnownTheme } from 'theme'; import { useSettings } from 'state/useSettings'; /** * Keep the account's theme and this browser's theme agreed, in both directions. * * ── Why it is two effects and not one ── * * They answer different questions. HYDRATE asks "this browser has never chosen — what does the account * say?" and runs once. PERSIST asks "the person just chose — tell the account", and runs on every change. * Collapsing them produces the bug this file used to have: the server's value re-applied on every settings * fetch, so choosing Plain and reloading silently reverted it. * * A local choice wins on load because it is the more recent statement of intent AND it is already on the * screen — re-applying the server's value would be a visible flip a second after the page settles. * * ── Migration ── * * Accounts predating themes stored `appearance.colorMode`. It is read once, mapped to the matching Duck * theme, and never written again. Somebody who chose dark a year ago keeps dark. * * Worth knowing: the server's default was `appearance: { theme: 'light' }` while the client read * `appearance.colorMode`, so that default had never been read by anything. Both sides say `theme` now. */ const LOCAL_KEY = 'officer-theme'; export const useColorModeSync = () => { const { settings, settingsLoaded, saveSettings } = useSettings(); const { themeId, setTheme } = useTheme(); const hydrated = useRef(false); // HYDRATE — once, and only into a browser that has not chosen for itself. useEffect(() => { // `settingsLoaded`, not `settings` — see its note. `settings` is the DEFAULTS until the request // lands, and acting on those is how this hook overwrote the account's real preference with the // default one and then saved it back. if (!settingsLoaded || hydrated.current) return; hydrated.current = true; try { if (window.localStorage.getItem(LOCAL_KEY)) return; } catch { // Storage unavailable. Honouring the account is the best guess left. } const saved = settings.appearance?.theme; if (isKnownTheme(saved)) { setTheme(saved); return; } const migrated = themeFromLegacyColorMode(settings.appearance?.colorMode); if (migrated) setTheme(migrated); }, [settingsLoaded, settings, setTheme]); // PERSIST — so the choice follows the account to another browser rather than living in one localStorage. useEffect(() => { if (!settingsLoaded || !hydrated.current) return; if (settings.appearance?.theme === themeId) return; saveSettings({ ...settings, appearance: { ...settings.appearance, theme: themeId } }); }, [themeId, settingsLoaded, settings, saveSettings]); };