Adds a light theme as a second set of CSS custom-property tokens; existing dark Obsidian Control look is unchanged when dark is selected.
Adds a sun/moon toggle in the sidebar footer (above the existing collapse arrow). Persists choice in localStorage.
Default for new users: matches the OS via prefers-color-scheme. No JS needed on first paint — the media query covers it, so no flash.
Once the user clicks the toggle, their explicit choice (written as <html data-theme="light|dark">) wins over the OS pref.
How it works
:root keeps the dark tokens. :root[data-theme="light"] overrides them with light tokens. A @media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) { … } } block applies the same light tokens when the user has made no explicit choice.
components/theme_toggle.rs is a tiny Dioxus component. On mount it reads localStorage; if an explicit value is stored it applies it to the DOM to avoid an OS-vs-stored mismatch flash. On click it flips the value and writes both the DOM attribute and localStorage. All web_sys calls are gated behind #[cfg(feature = "web")] so the server build stays clean.
Three CSS rules that hardcoded near-black hex values (page dot grid, .code-block, graph stabilization overlay) get explicit light-mode overrides so they don't render as dark patches on white.
web-sys feature list extended with Storage, MediaQueryList, Element so the toggle can do its job.
Test plan
Visit dashboard with a light-OS browser, no stored pref: page renders light immediately, no flash, sidebar shows moon icon ("Dark mode" label when expanded).
## Summary
- Adds a **light theme** as a second set of CSS custom-property tokens; existing dark Obsidian Control look is unchanged when dark is selected.
- Adds a **sun/moon toggle** in the sidebar footer (above the existing collapse arrow). Persists choice in `localStorage`.
- **Default for new users:** matches the OS via `prefers-color-scheme`. No JS needed on first paint — the media query covers it, so no flash.
- Once the user clicks the toggle, their explicit choice (written as `<html data-theme="light|dark">`) wins over the OS pref.
## How it works
- `:root` keeps the dark tokens. `:root[data-theme="light"]` overrides them with light tokens. A `@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) { … } }` block applies the same light tokens when the user has made no explicit choice.
- `components/theme_toggle.rs` is a tiny Dioxus component. On mount it reads `localStorage`; if an explicit value is stored it applies it to the DOM to avoid an OS-vs-stored mismatch flash. On click it flips the value and writes both the DOM attribute and `localStorage`. All `web_sys` calls are gated behind `#[cfg(feature = "web")]` so the server build stays clean.
- Three CSS rules that hardcoded near-black hex values (page dot grid, `.code-block`, graph stabilization overlay) get explicit light-mode overrides so they don't render as dark patches on white.
- `web-sys` feature list extended with `Storage`, `MediaQueryList`, `Element` so the toggle can do its job.
## Test plan
- [ ] Visit dashboard with a light-OS browser, no stored pref: page renders light immediately, no flash, sidebar shows moon icon ("Dark mode" label when expanded).
- [ ] Click moon → page goes dark, icon becomes sun, `localStorage["compliance-scanner.theme"] === "dark"`, `<html data-theme="dark">`.
- [ ] Refresh: still dark (explicit pref wins).
- [ ] Click sun → back to light, attribute + storage flip.
- [ ] Visit with dark-OS browser, no stored pref: renders dark (current behavior, unchanged).
- [ ] Inspect collapsed sidebar: toggle still visible and centered, label hidden.
- [ ] Visit a graph page in light mode: stabilization overlay is light, not a dark splash.
- [ ] Visit a finding detail with `.code-block` in light mode: code block has light bg, dark text.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Introduces a light theme alongside the existing dark Obsidian Control
look, plus a sun/moon toggle in the sidebar footer.
The dashboard's CSS already drove every surface through custom
properties on :root, so the light theme is added as a second token set
under `:root[data-theme="light"]` and, in parallel, inside a
`@media (prefers-color-scheme: light)` block guarded by
`:not([data-theme="dark"])`. Net effect:
- A user with no stored preference gets their OS theme via the media
query (no flash, no JS required).
- A user who clicked the toggle gets `data-theme="light|dark"` set on
`<html>`, which wins over the media query.
The toggle component (`theme_toggle.rs`) reads `localStorage` first
then `prefers-color-scheme` on mount, and writes both the DOM
attribute and `localStorage` on click. All `web_sys` calls are gated
behind `#[cfg(feature = "web")]` so the server build stays clean.
Three CSS rules that hardcoded near-black hex values (the page dot
grid, `.code-block`, and the graph stabilization overlay) get explicit
light-mode overrides so they don't render as dark patches on white.
web-sys feature list extended with Storage, MediaQueryList, and
Element so the toggle can read the media query and set the attribute.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sharang
merged commit a8cef58e02 into main2026-05-13 11:44:23 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
localStorage.prefers-color-scheme. No JS needed on first paint — the media query covers it, so no flash.<html data-theme="light|dark">) wins over the OS pref.How it works
:rootkeeps the dark tokens.:root[data-theme="light"]overrides them with light tokens. A@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) { … } }block applies the same light tokens when the user has made no explicit choice.components/theme_toggle.rsis a tiny Dioxus component. On mount it readslocalStorage; if an explicit value is stored it applies it to the DOM to avoid an OS-vs-stored mismatch flash. On click it flips the value and writes both the DOM attribute andlocalStorage. Allweb_syscalls are gated behind#[cfg(feature = "web")]so the server build stays clean..code-block, graph stabilization overlay) get explicit light-mode overrides so they don't render as dark patches on white.web-sysfeature list extended withStorage,MediaQueryList,Elementso the toggle can do its job.Test plan
localStorage["compliance-scanner.theme"] === "dark",<html data-theme="dark">..code-blockin light mode: code block has light bg, dark text.🤖 Generated with Claude Code