fix(theme): migrate persisted state from removed theme ids (#491)

PR #490 dropped five community themes (amber-night, ice-blue, monochrome,
phosphor-green, rose-dark). Existing users who had any of those selected
land on a non-existent data-theme attribute after the update — the
browser silently falls back to :root defaults and the picker shows the
old id as inactive in the list.

Add a Zustand persist `migrate` hook (version 1) that remaps the removed
ids to the closest surviving palette per family — gold for amber, carbon
grey for ice / monochrome, deep forest for phosphor green, sakura night
for rose. Applies to `theme`, `themeDay` and `themeNight` (theme
scheduler), so a scheduled night theme that was set to a removed id is
remapped too.

New installs are unaffected (migrate runs against persisted state only).
This commit is contained in:
Frank Stellmacher
2026-05-07 02:46:25 +02:00
committed by GitHub
parent f82f1be63a
commit 38b89f9730
+28
View File
@@ -44,6 +44,23 @@ export function getScheduledTheme(state: Pick<ThemeState, 'enableThemeScheduler'
return isDay ? state.themeDay : state.themeNight;
}
/** Themes removed in PR #490 (community theme redesign). Each key maps to the
* closest surviving palette so persisted state from older builds doesn't land
* on a non-existent `data-theme` attribute and silently fall back to :root. */
const REMOVED_THEME_REMAP: Record<string, string> = {
'amber-night': 'obsidian-gold', // warm gold/amber dark family
'ice-blue': 'carbon-grey', // cool neutral dark (no surviving cyan)
'monochrome': 'carbon-grey', // neutral grey dark
'phosphor-green': 'deep-forest', // green dark family
'rose-dark': 'sakura-night', // pink/rose dark family
};
function remapTheme(value: unknown): unknown {
return typeof value === 'string' && value in REMOVED_THEME_REMAP
? REMOVED_THEME_REMAP[value]
: value;
}
export const useThemeStore = create<ThemeState>()(
persist(
(set) => ({
@@ -74,6 +91,17 @@ export const useThemeStore = create<ThemeState>()(
}),
{
name: 'psysonic_theme',
version: 1,
migrate: (persistedState, _version) => {
if (!persistedState || typeof persistedState !== 'object') return persistedState;
const s = persistedState as Record<string, unknown>;
return {
...s,
theme: remapTheme(s.theme),
themeDay: remapTheme(s.themeDay),
themeNight: remapTheme(s.themeNight),
};
},
}
)
);