WFDASH-WEBAPP/src/components/theme-bootstrap.tsx

32 lines
1.2 KiB
TypeScript
Raw Normal View History

feat(theme): the picker reads the real palette, not a hand-kept copy The theme registry carried a hand-maintained set of hex swatches for every palette, and they had drifted away from the CSS the app actually paints. The picker previewed colours the product never showed. The registry now holds only what cannot be read from CSS — id, label, tagline, fonts, radius — and the picker, the Settings swatches and the preview cards resolve --background, --primary and --foreground off the theme itself. A preview can no longer disagree with the screen it previews. themes.css is rewritten so every palette carries a real dark surface, and the palettes that are dark by nature stay dark whichever way the mode switch is set. The pre-hydration script validates the stored theme id against the registry, so a retired id falls back to WFDASH instead of painting an unstyled page, and the old "warm" mode migrates to Light. The legacy text-{warn,success,info}-foreground classes now resolve to the semantic text colour and switch to the filled ink only on an exact solid fill, so one class reads correctly on both a tinted badge and a filled button. A solid focus outline replaces the faint ring, and the charts and quote status badges take their colours from tokens rather than hard-coded dark strokes. Tests: theme-bootstrap.test.tsx covers saved-theme recovery and the storage-blocked path; e2e/themes.spec.ts walks every palette in both modes checking surface contrast, tinted and solid semantic pairs, focus colour, chart tokens and the destructive hover, and asserts that the picker preview matches the applied theme.
2026-11-04 22:40:40 +00:00
import { DEFAULT_THEME_ID, THEMES } from "@/lib/themes";
/**
* Inline script that runs in <head> before hydration.
*
* Reads the persisted theme id from localStorage and applies it to
* <html> so the user never sees a flash of the default theme.
*
feat(theme): the picker reads the real palette, not a hand-kept copy The theme registry carried a hand-maintained set of hex swatches for every palette, and they had drifted away from the CSS the app actually paints. The picker previewed colours the product never showed. The registry now holds only what cannot be read from CSS — id, label, tagline, fonts, radius — and the picker, the Settings swatches and the preview cards resolve --background, --primary and --foreground off the theme itself. A preview can no longer disagree with the screen it previews. themes.css is rewritten so every palette carries a real dark surface, and the palettes that are dark by nature stay dark whichever way the mode switch is set. The pre-hydration script validates the stored theme id against the registry, so a retired id falls back to WFDASH instead of painting an unstyled page, and the old "warm" mode migrates to Light. The legacy text-{warn,success,info}-foreground classes now resolve to the semantic text colour and switch to the filled ink only on an exact solid fill, so one class reads correctly on both a tinted badge and a filled button. A solid focus outline replaces the faint ring, and the charts and quote status badges take their colours from tokens rather than hard-coded dark strokes. Tests: theme-bootstrap.test.tsx covers saved-theme recovery and the storage-blocked path; e2e/themes.spec.ts walks every palette in both modes checking surface contrast, tinted and solid semantic pairs, focus colour, chart tokens and the destructive hover, and asserts that the picker preview matches the applied theme.
2026-11-04 22:40:40 +00:00
* The emitted script is self-contained; its allowlist comes from the registry.
*/
export function ThemeBootstrapScript() {
const code = `
(function() {
try {
feat(theme): the picker reads the real palette, not a hand-kept copy The theme registry carried a hand-maintained set of hex swatches for every palette, and they had drifted away from the CSS the app actually paints. The picker previewed colours the product never showed. The registry now holds only what cannot be read from CSS — id, label, tagline, fonts, radius — and the picker, the Settings swatches and the preview cards resolve --background, --primary and --foreground off the theme itself. A preview can no longer disagree with the screen it previews. themes.css is rewritten so every palette carries a real dark surface, and the palettes that are dark by nature stay dark whichever way the mode switch is set. The pre-hydration script validates the stored theme id against the registry, so a retired id falls back to WFDASH instead of painting an unstyled page, and the old "warm" mode migrates to Light. The legacy text-{warn,success,info}-foreground classes now resolve to the semantic text colour and switch to the filled ink only on an exact solid fill, so one class reads correctly on both a tinted badge and a filled button. A solid focus outline replaces the faint ring, and the charts and quote status badges take their colours from tokens rather than hard-coded dark strokes. Tests: theme-bootstrap.test.tsx covers saved-theme recovery and the storage-blocked path; e2e/themes.spec.ts walks every palette in both modes checking surface contrast, tinted and solid semantic pairs, focus colour, chart tokens and the destructive hover, and asserts that the picker preview matches the applied theme.
2026-11-04 22:40:40 +00:00
var validIds = ${JSON.stringify(THEMES.map((t) => t.id))};
var themeId = localStorage.getItem('wfdash-theme');
if (validIds.indexOf(themeId) === -1) themeId = ${JSON.stringify(DEFAULT_THEME_ID)};
var root = document.documentElement;
root.setAttribute('data-theme', themeId);
feat(theme): the picker reads the real palette, not a hand-kept copy The theme registry carried a hand-maintained set of hex swatches for every palette, and they had drifted away from the CSS the app actually paints. The picker previewed colours the product never showed. The registry now holds only what cannot be read from CSS — id, label, tagline, fonts, radius — and the picker, the Settings swatches and the preview cards resolve --background, --primary and --foreground off the theme itself. A preview can no longer disagree with the screen it previews. themes.css is rewritten so every palette carries a real dark surface, and the palettes that are dark by nature stay dark whichever way the mode switch is set. The pre-hydration script validates the stored theme id against the registry, so a retired id falls back to WFDASH instead of painting an unstyled page, and the old "warm" mode migrates to Light. The legacy text-{warn,success,info}-foreground classes now resolve to the semantic text colour and switch to the filled ink only on an exact solid fill, so one class reads correctly on both a tinted badge and a filled button. A solid focus outline replaces the faint ring, and the charts and quote status badges take their colours from tokens rather than hard-coded dark strokes. Tests: theme-bootstrap.test.tsx covers saved-theme recovery and the storage-blocked path; e2e/themes.spec.ts walks every palette in both modes checking surface contrast, tinted and solid semantic pairs, focus colour, chart tokens and the destructive hover, and asserts that the picker preview matches the applied theme.
2026-11-04 22:40:40 +00:00
// Older versions stored "warm". Migrate before next-themes reads it.
var mode = localStorage.getItem('wfdash-mode');
if (mode && mode !== 'light' && mode !== 'dark') {
localStorage.setItem('wfdash-mode', 'light');
}
} catch (e) {
// localStorage may be blocked — fall back to the SSR-rendered theme
}
})();
`;
return <script dangerouslySetInnerHTML={{ __html: code }} />;
}