diff --git a/CLAUDE.md b/CLAUDE.md index c6ccf00..ae7f2bf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -803,7 +803,11 @@ Theme state lives in `ui/src/theme.jsx` — a React context (`ThemeContext`) wit - **Toggle:** `setDark(d => !d)` in `StatusBar.jsx`; effect writes `localStorage` and toggles the `.dark` class on `` - **CSS:** `ui/src/index.css` defines CSS custom properties under `:root` (light) and `.dark`. All Tailwind color overrides are written as `.dark .bg-white { ... }` etc. — no Tailwind dark-mode config needed - **Palette:** dark mode uses Perspective's "Pro Dark" colours (`--bg-primary: #242526`, panels `#2a2c2f`, gridlines `#3b3f46`, text `#c5c9d0`) -- **Perspective viewer:** `Forecast.jsx` calls `viewer.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light')` both on initial load and in a `useEffect([dark, versionId])` so the viewer stays in sync when the toggle fires +- **Perspective viewer:** `Forecast.jsx` calls `viewer.restore({ theme: dark ? 'Pro Dark' : 'Pro Light' })` both on initial load and in a `useEffect([dark, versionId])`. A **partial** restore, so it does not rebuild the view the way `applyDepth`'s whole-config one does. + + It used to be `setAttribute('theme', …)`, which in 5.4.0 does **nothing** — the theme is a `ViewerConfig` field (`ViewerConfig.theme`, a theme *name*), and the element observes no such attribute. The pivot simply stayed light whatever the toggle said. + + Half the fix is in `cleanLayout`, which **drops `theme`**. `viewer.save()` includes it, so every stored layout had one baked in, and restoring one reasserted the author's theme and undid the toggle. It is the reader's choice, not a property of the layout — which also means a published layout cannot impose its theme on whoever applies it. `sameConfig()` ignores `theme` for the same reason: toggling is not a layout edit and must not light the dirty dot. - **Consuming the theme:** `import useTheme from '../theme.jsx'` then `const { dark, setDark } = useTheme()` ## After a change: restart, and Generate SQL diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index 0d81545..45b77f2 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -449,7 +449,16 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio useEffect(() => { if (viewerRef.current) { - viewerRef.current.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light') + // restore(), not setAttribute('theme'): the theme is a ViewerConfig field + // in 5.4.0 (ViewerConfig.theme, a theme *name* like "Pro Dark"), and the + // element observes no such attribute -- the attribute call this replaced + // did nothing at all, so the pivot simply stayed light. + // + // A partial update: everything else is left as it is, so this does not + // rebuild the view the way applyDepth's whole-config restore does. + // cleanLayout drops `theme` on the way in, so no layout restore puts it + // back afterwards. + viewerRef.current.restore({ theme: dark ? 'Pro Dark' : 'Pro Light' }) } }, [dark, versionId]) @@ -923,7 +932,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio console.error('[setAutoPause]', err) } - viewer.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light') + await viewer.restore({ theme: dark ? 'Pro Dark' : 'Pro Light' }) if (!hideSplitTotal()) setTimeout(hideSplitTotal, 400) // restore last-used layout or build default