feat(dashboard): live theme preview in ThemeSelectorModal
When the modal is open, the targeted component re-renders with the candidate theme as soon as the user picks an option — no dashboard- dirty round-trip required. Cancel reverts; Apply commits to Redux. Implementation: - New \`previewThemeStore\` (module-level subscribable map keyed by layoutId, distinguishing "no preview" / "preview value=null" / "preview value=number"). Tiny surface: \`set\`/\`clear\`/\`get\`/ \`subscribe\`. No-op \`set\` / \`clear\` calls don't fire listeners. - \`useEffectiveThemeId\` now subscribes via \`useSyncExternalStore\` and prefers the preview value over the Redux-resolved id when present. - \`ThemeSelectorModal\` writes the in-flight selection through the store as the user picks options; cleanup on close (Cancel, X button, escape) clears it. Apply dispatches the Redux action *before* hiding, so the post-cleanup re-resolution lands on the saved value (no flicker). Snapshot of the resolved id at open-time goes through a \`useRef\` because \`currentThemeId\` itself becomes reactive (it would already reflect the in-flight preview), so we can't read it for "what should we revert to?". 6 new tests for the preview store: get-undefined-for-unknown, set-stores-numeric, set-stores-explicit-null, clear-removes, subscriber-fires-on-real-change-and-not-on-no-op, multi-layoutId- independence. Total dashboard ComponentThemeProvider suite is now 14 passing tests. Drops "live preview" from the deferred-items list in SIP.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C
Claude committed
ad37366e93e69b68e77d85aa5bbf0a2849295069
Parent: 1be84f1