fix+feat: theme propagation into OpenCode embed + Solarized/Latte themes + light/dark/auto mode (#589, #585)#595
Conversation
…iveapp#589) OpenCode mounts inside a Shadow DOM so the document-level theme class and SpaceUI CSS custom properties never reached it — the workbench prompt-input + buttons stayed dark on every Spacebot theme, illegible on vanilla. Three-layer fix: 1. interface/src/components/OpenCodeEmbed.tsx - Forward all SpaceUI `--color-*` tokens into a `<style>` element inside the shadow root as `:host { --color-app-box: …; … }` so the embed's CSS can resolve var() refs against the active theme. - Re-runs on every theme change without remounting (preserves OpenCode session state). - Pass colorScheme derived from the host theme (`vanilla` → light, others → dark) at mount, and call the new `handle.setColorScheme` reactively when the host theme changes. 2. interface/opencode-embed-src/embed.tsx - Convert ThemeInjector to read `props.colorScheme` reactively via createEffect (was onMount, fired once). - mountOpenCode creates a SolidJS signal for colorScheme so the handle's new `setColorScheme(scheme)` method can update the ThemeProvider without remounting the SolidJS app. - Extend MountOpenCodeHandle with setColorScheme. 3. interface/opencode-embed-src/spacebot-theme.json - Light scheme had identical syntax/text/link overrides to the dark scheme — bright lime/lavender/light-blue values that read fine on dark backgrounds but failed WCAG AA on near-white. Tuned light overrides for ≥6:1 contrast against vanilla's app-box (most ≥8:1): syntax-string → #066b30 syntax-primitive → #a00808 syntax-type → #5d3800 syntax-constant → #0a4d85 syntax-info → #0a4d85 syntax-property → #5818b8 syntax-comment → #5a5a66 text-weaker → #5a5a66 markdown-code → #a00808 text-interactive-{base,strong}, markdown-link, markdown-link-text → #5818b8 - Dark scheme overrides unchanged. Verified end-to-end on a dev VM: - Vanilla theme renders the embed with light backgrounds + readable syntax colors. Switching to midnight/noir/slate hot-swaps the embed to dark instantly without losing the OpenCode session. - Static contrast check: 23/24 light text tokens at WCAG AA, syntax tokens 6.4–9.9 ratio. Sole remaining outlier is markdown-image-text (alt-text fallback, decorative). Closes spacedriveapp#589
|
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughThe PR enables runtime color scheme synchronization between the host dashboard theme and the embedded OpenCode app. The embedded Solid component exposes a reactive setter, the React host forwards Spacebot CSS variables into the embed Shadow DOM, and theme mode/persistence, Appearance UI, and new theme CSS assets are added. ChangesEmbed theme reactivity
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…edriveapp#585) Adds three new themes and replaces the single-theme picker with a mode selector + per-mode theme picks that follow the OS prefers-color-scheme setting in `auto` mode. ## New themes - `solarized-light` — Ethan Schoonover's classic light palette - `solarized-dark` — its dark counterpart - `catppuccin-latte` — Catppuccin's official light variant, paired with the existing mocha (Catppuccin Mocha) dark theme Token CSS lives in `interface/src/themes/` and imported from `styles.css`, matching the per-theme class-selector convention used by `@spacedrive/tokens`. A future PR can move these upstream once the mappings settle. ## Mode selector `useTheme.ts` rewritten to expose: - `mode: "light" | "dark" | "system"` — single switch - `lightTheme` / `darkTheme` — independent per-mode choices - `theme` — computed effective id (consumers stay simple) - `isLight` — computed boolean for theme-aware downstream logic A `prefers-color-scheme` MediaQueryList listener recomputes the effective theme automatically when the OS toggles dark mode. ## Settings UI `AppearanceSection` now shows: - Mode selector at top (Light / Dark / Auto) - One theme picker in `light` or `dark` mode (filtered to the matching `isLight`) - Two theme pickers side-by-side in `auto` mode (so users pick which light + which dark theme the system uses for each OS state) - An "active" badge on whichever picker reflects what's currently rendering ## Migration The legacy single-key format `localStorage["spacebot-theme"]` migrates automatically on first load to the new tri-key model: - `spacebot-theme-mode = "system"` - `spacebot-theme-light = <legacy if isLight>` (or default `vanilla`) - `spacebot-theme-dark = <legacy if !isLight>` (or default `default`) Migration runs at module import time (before React hydrates), is one-shot (legacy key removed after writes succeed), and is idempotent. Logs a single `console.info` for visibility. SSR-safe. ## Out of scope (separate PRs) - Custom user-authored themes - Per-component theme overrides - Upstreaming Solarized + Latte to `@spacedrive/tokens`
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@interface/src/components/settings/AppearanceSection.tsx`:
- Around line 86-115: The mode/theme picker uses buttons with role="radio" but
lacks full native radio semantics and keyboard behavior; update the UI in
AppearanceSection (where MODE_OPTIONS, mode, and setMode are used) to use actual
radio inputs (input type="radio") grouped in a fieldset/role="radiogroup" or
implement the full ARIA radio keyboard pattern (handle
ArrowLeft/ArrowRight/ArrowUp/ArrowDown and Space/Enter, manage tabindex for each
option, and expose aria-checked) so assistive tech reliably announces selection;
ensure each option binds to the same name, reflects mode state, calls
setMode(opt.id) on change, and keep the existing visual styling by visually
hiding the native input if needed while preserving focus and accessibility.
In `@interface/src/hooks/useTheme.ts`:
- Around line 128-150: During migration in useTheme.ts, preserve the old
single-theme behavior by assigning the legacy theme to both slots so the user
stays on their chosen surface regardless of OS mode; when legacyTheme is found
(from LEGACY_KEY via getThemeById), set lightTheme = legacyTheme.id and
darkTheme = legacyTheme.id (instead of only setting the slot matching
legacyTheme.isLight), then set mode = "system", persist MODE_KEY, LIGHT_KEY,
DARK_KEY, remove LEGACY_KEY and keep the existing console.info call (referencing
mode, lightTheme, darkTheme, DEFAULT_LIGHT, DEFAULT_DARK).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4f6d9f02-a69d-44b9-9c22-3971e677687b
📒 Files selected for processing (6)
interface/src/components/settings/AppearanceSection.tsxinterface/src/hooks/useTheme.tsinterface/src/styles.cssinterface/src/themes/catppuccin-latte.cssinterface/src/themes/solarized-dark.cssinterface/src/themes/solarized-light.css
✅ Files skipped from review due to trivial changes (4)
- interface/src/styles.css
- interface/src/themes/solarized-dark.css
- interface/src/themes/catppuccin-latte.css
- interface/src/themes/solarized-light.css
useTheme.ts migration regression
--------------------------------
Migrating legacy spacebot-theme to mode = "system" silently flips users to
the default theme of the opposite surface. A user who had picked nord (dark)
lands on vanilla whenever the OS is in light mode, because the untouched
light slot falls back to DEFAULT_LIGHT.
Pin mode to the surface the user explicitly chose ("light" or "dark") based
on legacyTheme.isLight. Auto/system mode is still available — they just have
to opt in.
AppearanceSection a11y — native radios
--------------------------------------
Mode + theme pickers used <button role="radio"> without the full ARIA radio
keyboard pattern (no arrow-key navigation, no aria-checked on theme cards).
Replace with native <input type="radio"> inside <fieldset>: browsers handle
arrow-key cycling, space/enter activation, and screen-reader announcements
("radio button, X of Y, selected") natively. Visual styling unchanged
(input is sr-only, label is the visible card; focus ring on focus-within).
CodeRabbit flagged docstring coverage at 43.75% on this PR's changed files (threshold 80%). Added one-line docstrings on the public surface of the two files I just touched: useTheme exports, internal helpers in useTheme, and the four components in AppearanceSection. Kept docstrings short and WHY-leaning where there's something non-obvious (e.g. why getSystemPrefersDark defaults to dark on SSR; why ThemePreview uses static colors instead of resolved CSS vars).
Pushed minimal one-liners on the public surface of Suggesting we either:
Either is a one-click change in the CodeRabbit settings — there's no |
Closes #589 and #585. Two related theme fixes; combined here because the new themes added in #585 only render correctly inside the OpenCode embed once the propagation work from #589 is in place.
Part 1 — Theme propagation into OpenCode embed (#589)
OpenCode mounts inside a Shadow DOM in
OpenCodeEmbed.tsx, so the document-level theme class and SpaceUI CSS custom properties never reached it. Workbench prompt-input + buttons stayed dark on every Spacebot theme — most visibly broken onvanilla(light), structurally wrong on every theme.Three layers
interface/src/components/OpenCodeEmbed.tsx— forward CSS vars + colorScheme<style>element into the shadow root that mirrors all SpaceUI--color-*tokens as:host { --color-app-box: …; … }so the embed's CSS resolvesvar()against the active theme.colorSchemederived from the host theme (isLight→lightelsedark) at mount, and call the newhandle.setColorSchemereactively when the host theme changes.interface/opencode-embed-src/embed.tsx— reactive colorSchemeThemeInjectorswitched fromonMounttocreateEffectso it re-runs whenprops.colorSchemeupdates.mountOpenCodecreates a SolidJS signal for colorScheme; the returned handle exposes a newsetColorScheme(scheme)method that updates that signal.MountOpenCodeHandleextended withsetColorScheme.interface/opencode-embed-src/spacebot-theme.json— light-theme contrastlight.overrideswas identical todark.overrides— bright lime/lavender/light-blue values that fail WCAG AA on the near-white vanilla background. Tuned light overrides for ≥6:1 contrast against vanilla's--color-app-box(most ≥8:1):syntax-string#5cf0b0#066b30syntax-primitive#f06060#a00808syntax-type#f0c070#5d3800syntax-constant#70d0f0#0a4d85syntax-info#70d0f0#0a4d85syntax-property#c080f0#5818b8syntax-commentvar(--color-ink-faint)#5a5a66text-weakervar(--color-ink-faint)#5a5a66markdown-code#5cf0b0#a00808text-interactive-{base,strong},markdown-link,markdown-link-textvar(--color-accent)#5818b8Dark overrides unchanged.
Part 2 — New themes + light/dark/auto mode (#585)
New theme tokens
CSS files under
interface/src/themes/, imported fromstyles.css:solarized-light— Ethan Schoonover's classic light palette (Solarized Light, blue accent)solarized-dark— its dark counterpartcatppuccin-latte— Catppuccin's official light variant pairing the existingmocha(Catppuccin Mocha)Mappings translate Solarized's 16 named colors and Catppuccin's 26 named colors into SpaceUI's token surface (
--color-app,--color-ink,--color-app-line, etc.). Local override files for now; can be moved upstream to@spacedrive/tokensin a follow-up.Mode selector
useTheme.tsrewritten to expose:mode: "light" | "dark" | "system"— single switchlightTheme/darkTheme— independent per-mode choicestheme— computed effective id (consumers stay simple)isLight— computed boolean (used bycolorSchemeForThemefrom Part 1, so any new light theme propagates into OpenCode automatically)A
prefers-color-schemeMediaQueryListlistener recomputes the effective theme when the OS toggles dark mode.Settings UI
AppearanceSectionshows:lightordarkmode (filtered byisLight)automode (so users pick which light + which dark theme the system uses for each OS state)Migration
Legacy
localStorage["spacebot-theme"]migrates automatically on first load to the new tri-key model:spacebot-theme-mode = "system"spacebot-theme-light = <legacy if isLight, else default vanilla>spacebot-theme-dark = <legacy if !isLight, else defaultdefault>Migration runs at module import time (before React hydrates → no FOUC), is one-shot (legacy key removed only after writes succeed), idempotent across reloads, and SSR-safe. Logs a single
console.infowhen it runs for debugging visibility.Verification
End-to-end on a dev VM:
prefers-color-schemelistener verified by toggling macOS Appearance: dashboard auto-swaps inautomodeOut of scope (future PRs)
@spacedrive/tokensscripts/build-opencode-embed.shauto-invocation frombuild.rs(currently a documented manual step beforecargo build)