feat(web): add modular theme library - #5226
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
e0f79e4 to
607dd6b
Compare
ApprovabilityVerdict: Needs human review Diff is too large for automated approval analysis. A human reviewer should evaluate this PR. You can customize Macroscope's approvability policy. Learn more. |
|
I did not have time to go through this PR thoroughly. But the general direction taken is exactly what I had in mind when I started #418. From what I see, it should be easy to create a custom theme based on the tokens created. |
Adds semantic theme roles, persisted light and dark variants, personal theme import and creation, contrast-safe surfaces, and themed message actions. Includes the T3 Chat palette, follow-system behavior, splash handling, and the theme library UI.
…hemes The panel header becomes a drag handle (clamped to the viewport, and re-clamped on resize), replacing the left/right dock toggle. Imported VS Code themes get a "VS Code · " name prefix so their origin shows in the library. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clicking a one-appearance theme's card now picks its only half instead of making it the base for both appearances (which forced dark onto light mode), and the first-pick mirror skips appearances the theme cannot render. Imported VS Code themes drop the name prefix and read slug names as words; the Personal tag on cards is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The import dialog becomes Upload files / Paste JSON tabs. Several files import as a batch: a light and dark VS Code theme whose names differ only by the appearance word merge into one dual-mode theme, ambiguous families and loners stay individual, and batches install without activating. The settings button reads Import theme. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ports Importing a theme that is already installed now asks whether to update it in place or keep both (the copy takes the source file's name when that differs, else a v2 suffix); updating a theme that is showing re-applies it. Batches where several files share one internal name -- Dracula ships dracula.json and dracula-soft.json that both say "Dracula" -- are told apart by their file names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A pickThemeFiles bridge method drives a native multi-select JSON picker that starts in ~/.vscode/extensions when it exists (the same dotfolder on Windows, macOS, and Linux) and reads the files in the main process, size-capped. The web import dialog uses it when the bridge offers it and falls back to the plain file input elsewhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
One finding: the new pickFiles operation reuses the folder-picker error tag, so the failure is no longer structurally identified by its tag or message. See the inline comment.
Posted via Macroscope — Effect Service Conventions
… accent The diff panel's code-token surface mapping moves to a shared constant that the file preview now applies too, so its gutter, rows, and body follow the theme instead of the renderer's bundled palette. Imported VS Code themes derive their fallback roles from a muted accent: the vivid ramp carries the accent hue into every surface, which painted a neutral gray theme's code pane and text blue. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… picker A resolved mix half counts as a selection in the boot splash even when the base theme is gone; theme names whose displayName humanizes to nothing fall through to the name; a light/dark pair whose stripped name collides with a reserved id stays two single themes instead of failing the batch; importing a single-appearance theme activates its half like a card click; and the desktop file picker gets its own error tag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The file row and the JSON editor share one view again, with the row as the drop target; the tab switcher and the dialog subtitle are gone. Multi-file drops and VS Code conversion are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generated and imported themes inherited the built-in error and warning roles, so the destructive button stayed brand pink on every palette -- the remove-theme dialog being the visible case. Both engines now solve a red and an amber against their own canvas: the solid tone stays dark enough for the white label those buttons use, and the surface and foreground pair keeps alert contrast. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Solving red and amber against the canvas alone landed every theme on the same tone. Each family now leans up to 14 degrees toward the accent and picks up its chroma, so Grove reads warm, Ocean and Iris crimson, Ember orange-red -- still unmistakably a destructive red. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generated and imported palettes inherited their status roles from the flagship theme, whose dark error is a brand pink -- that is what turned every destructive button pink. They now fall back to the red and amber the app shows unthemed, with surfaces laid over the theme's own canvas. A theme file that names its own error or warning still wins. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The standard pair was chosen by the slot while the rest of the palette follows the canvas, so an inverted seed (dark canvas saved as a light theme) got the light pair on a dark alert surface. Both engines now pick by canvas luminance, and the standard foreground is nudged in lightness when a tinted surface would leave it under 4.5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Merge light and dark palettes into existing themes - Add explanatory tooltips and palette collision feedback - Use numbered names for imported theme copies
- Add a draggable corner grip with viewport-clamped dimensions - Preserve minimized layout while retaining the chosen panel width
The splash paints the theme's flat canvas again; the accent glow overlay is gone. The boot script still publishes --boot-accent for the parity tests and future use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The grip capped width and height against the whole viewport, so a panel parked away from the top-left could push its far edges -- and the grip itself -- off screen. The room to grow is now measured from the corner the resize holds still. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-system # Conflicts: # apps/web/src/components/SidebarV2.tsx
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2033017. Configure here.
| settings.sidebarThreadPreviewCount, | ||
| settings.timestampFormat, | ||
| settings.wordWrap, | ||
| followSystem, |
There was a problem hiding this comment.
Restore misses theme mix dirty
Medium Severity
changedSettingLabels includes a “Theme mix” entry from themeHalves, but themeHalves is omitted from the useMemo dependency list. Mixing light/dark themes without changing the base preference or follow-system flag can leave Restore defaults disabled and omit the mix from the confirm list.
Reviewed by Cursor Bugbot for commit 2033017. Configure here.


Summary
Adds a token-based theme system to the web client and desktop app: 57 semantic color roles, applied before React mounts so there is no flash of the wrong theme.
color()values, and pairs a family's light and dark files into one dual-mode theme. Re-importing an installed theme asks whether to update it or keep both.Closes #418
Closes #233
Closes #4302
Supersedes #5258
Screenshots
Both shots show the same mix — T3 Chat in light, Grove in dark — from either side of the System toggle.
The editor floats above the app and paints its draft on the live interface, so a color is judged by browsing the app rather than a miniature; it drags anywhere and survives navigation. Advanced mode lists one role per row behind a filter.
Built with GPT-5.6-Luna and Claude Fable 5 in T3 Code.
Note
Medium Risk
Large cross-cutting UI and localStorage theme state with boot/runtime parity requirements; desktop IPC reads user-selected JSON files but caps size—behavioral changes to
setTheme(clears mix, boolean return) affect restore and editor flows.Overview
Introduces a modular theme system that replaces the simple light/dark/system select in Appearance with a ThemeLibrary: built-in palettes, custom themes, light/dark half-mixing, guided and advanced editors (
ThemeColorPicker,ThemeEditorHost), and import/export—including VS Code theme JSON on desktop via newpickThemeFilesIPC (ElectronDialog.pickFiles, size-capped reads, default~/.vscode/extensions).index.htmlboot script now mirrors runtime theme resolution (legacy IDs, custom themes from localStorage, mix halves) and paints the splash with--boot-*variables before React mounts.Semantic tokens (
text-icon-muted,text-secondary-label,text-placeholder,bg-message,bg-message-action,--code-background,--terminal-*, error/warning/update) replace ad-hoc opacity classes across sidebar, composer, timeline, and settings; diffs and file preview shareDIFF_SURFACE_THEME_UNSAFE_CSSand code-surface variables; terminals read--terminal-*from the document theme.Smaller UX fixes: workspace titlebar controls use a consistent inset; sidebar headers sit above stage backdrop bleed; composer send drops stage backdrop art for
message-actionstyling; restore-defaults rolls back theme/mix/follow-system with explicit failure handling.Reviewed by Cursor Bugbot for commit 2033017. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add modular theme library with custom theme creation, import, and per-appearance mixing
ThemeLibrarycomponent supporting custom themes, per-appearance "halves" mixing, import/export, creation, and deletion.themePalette.tsas the core module defining typed theme roles, storage keys, built-in theme IDs, and APIs to parse, resolve, and manage theme definitions at runtime.ThemeEditorPanelfor interactively creating or editing themes with live preview, and aThemeImportDialogsupporting VS Code theme file import with color space conversion and overlay flattening.useThemeto returnsetTheme(now returns a success boolean), plus new APIs:setAppearanceMode,setFollowSystem,setThemeHalf,clearThemeHalves, andrefreshTheme.index.htmlboot script to resolve built-in and custom themes (including halves mixing) before React loads, applying splash colors anddarkclass from stored preferences.desktop:pick-theme-files) so the Electron app can open a file picker and return JSON theme file contents to the renderer.muted-foregroundshades to semantic tokens (text-secondary-label,text-icon-muted,text-placeholder,text-message-foreground,text-error,text-warning-foreground, etc.).setThemebehavior change — it now clears any stored theme halves when called, and returnsfalseinstead of throwing on storage failure.Macroscope summarized 2033017.