TypeScript-first theme tokens: replace Rust CSS parser with client-side token system#7218
TypeScript-first theme tokens: replace Rust CSS parser with client-side token system#7218aharvard wants to merge 3 commits intomcp-apps-stylingfrom
Conversation
Move theme token management from server-side Rust (theme_css.rs) to a TypeScript module (theme-tokens.ts) that serves as the single source of truth for both Goose desktop styling and MCP app host styles. Key changes: Server (goose-server): - Remove theme_css.rs Rust CSS parser - Remove /theme/variables and /config/theme API endpoints - Remove openapi entries for theme endpoints Theme system (ui/desktop): - Add theme-tokens.ts with typed light/dark token maps keyed by McpUiStyleVariableKey, enforced at compile time - Add applyThemeTokens() and buildMcpHostStyles() helpers - ThemeContext now applies tokens via style.setProperty() instead of fetching from the server and injecting a <style> tag - Support localStorage theme-overrides with tokenVersion reactivity - Expose mcpHostStyles and refreshTokens from ThemeContext - Pass mcpHostStyles to McpAppRenderer for MCP app theming - Add tokensUpdated flag to cross-window theme broadcasts CSS (main.css): - Restructure into numbered sections (primitives, MCP token registration, Goose aliases, light baseline, dark baseline, components) - Register all McpUiStyleVariableKey tokens in @theme inline for Tailwind utility class generation - Add full light (:root) and dark (.dark) baseline values - Add scrollbar, sidebar, and reduced-motion styles Also adds: - ThemeColorEditor component for live token editing (dev tool) - PROPOSAL-theme-tokens.md design doc
The accent token aliases (background-accent, border-accent, text-accent, text-on-accent) were bridging the old class names to the new inverse tokens. No TSX/TS files reference these utility classes — components already use the MCP-spec names (bg-background-inverse, text-text-inverse, etc.) directly. Remove the dead aliases to keep section 4 clean.
Replace old Tailwind utility classes with their MCP-compliant counterparts: - bg-background-default → bg-background-primary - border-border-default → border-border-primary - border-border-strong → border-border-secondary - bg-background-muted → bg-background-secondary - text-text-muted → text-text-secondary These old names had no @theme inline registration, so they resolved to nothing — causing transparent backgrounds and missing borders in both light and dark mode (e.g., chat input, speech bubbles, MCP app containers, recipe form fields).
|
|
I'm not confident that some of my class name changes that I applied on the JSX side of things are needed. That was kind of some rapid troubleshooting that I might end up reverting. My goal is to clean up the main.css file in such a way that makes it easy to maintain and extend. |
There was a problem hiding this comment.
Pull request overview
This PR migrates the theming system from a server-side Rust CSS parser to a TypeScript-first architecture. The change eliminates fragile regex-based CSS parsing and API round-trips, replacing them with type-safe token definitions in theme-tokens.ts that are applied directly to the DOM at runtime. Theme customizations now use localStorage instead of server-side file storage, and MCP apps receive theme variables via the hostContext.styles object using the light-dark() CSS format.
Changes:
- Removed server-side Rust CSS parser (
theme_css.rs) and theme API endpoints (/theme/variables,/theme/save) - Added
theme-tokens.tsas single source of truth with compiler-enforced type safety viaMcpUiStyleVariableKey - Implemented
ThemeColorEditorcomponent for live token editing with localStorage-based persistence - Restructured
main.cssinto clearly labeled sections (primitives, MCP token registration, app aliases, baselines) - Migrated legacy class names (
bg-background-default→bg-background-primary, etc.) - Added cross-window token synchronization via Electron IPC
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| ui/desktop/src/theme/theme-tokens.ts | New source of truth for all theme tokens with light/dark variants and MCP host styles builder |
| ui/desktop/src/styles/main.css | Restructured with clear sections: primitives, token registration, app aliases, baselines |
| ui/desktop/src/contexts/ThemeContext.tsx | Removed API fetch logic, added localStorage override support and token refresh mechanism |
| ui/desktop/src/components/settings/app/ThemeColorEditor.tsx | New interactive theme editor with live preview and localStorage persistence |
| ui/desktop/src/components/McpApps/McpAppRenderer.tsx | Integrated mcpHostStyles from ThemeContext into host context |
| ui/desktop/src/preload.ts | Added tokensUpdated flag to broadcastThemeChange IPC payload |
| ui/desktop/src/api/*.ts | Removed theme API types and functions (getThemeVariables, saveTheme) |
| ui/desktop/openapi.json | Removed /theme/variables and /theme/save endpoints |
| crates/goose-server/src/theme_css.rs | Deleted entire Rust CSS parser module |
| crates/goose-server/src/routes/config_management.rs | Removed theme API route handlers |
| crates/goose-server/src/openapi.rs | Removed theme API OpenAPI definitions |
| crates/goose-server/src/main.rs | Removed theme_css module declaration |
| crates/goose-server/src/lib.rs | Removed theme_css exports |
| Various TSX files | Updated class names from legacy to MCP-compliant semantic tokens |
Comments suppressed due to low confidence (1)
ui/desktop/src/components/McpApps/McpAppRenderer.tsx:460
- The hostContext useMemo uses mcpHostStyles but doesn't list it in the dependency array. While mcpHostStyles is built once at module level and never changes, it's best practice to include all referenced values in the dependency array for clarity and to prevent future bugs if the implementation changes.
const hostContext = useMemo((): McpUiHostContext => {
const context: McpUiHostContext = {
// todo: toolInfo: {}
theme: resolvedTheme,
styles: mcpHostStyles,
// 'standalone' is a Goose-specific display mode (dedicated Electron window)
// that maps to the spec's inline | fullscreen | pip modes.
displayMode: displayMode as McpUiDisplayMode,
availableDisplayModes:
displayMode === 'standalone' ? [displayMode as McpUiDisplayMode] : AVAILABLE_DISPLAY_MODES,
// todo: containerDimensions: {} (depends on displayMode)
locale: navigator.language,
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
userAgent: navigator.userAgent,
platform: 'desktop',
deviceCapabilities: {
touch: navigator.maxTouchPoints > 0,
hover: window.matchMedia('(hover: hover)').matches,
},
safeAreaInsets: {
top: 0,
right: 0,
bottom: 0,
left: 0,
},
};
return context;
}, [resolvedTheme, displayMode]);
| // Revert live preview | ||
| applyThemeTokens(resolvedTheme); | ||
| onClose(); | ||
| }, [resolvedTheme, onClose]); |
There was a problem hiding this comment.
The handleCancel function applies default tokens but doesn't restore previously saved localStorage overrides. This could cause a brief flash where saved custom colors revert to defaults before ThemeContext's useEffect reapplies them. Consider reading and applying the saved overrides directly here instead of calling applyThemeTokens.
| // Revert live preview | |
| applyThemeTokens(resolvedTheme); | |
| onClose(); | |
| }, [resolvedTheme, onClose]); | |
| // Revert live preview to the currently persisted theme tokens (including overrides) | |
| refreshTokens(); | |
| onClose(); | |
| }, [refreshTokens, onClose]); |
Combines the TypeScript-first token architecture (PR #7218) with Spencer's theme picker UX (PR #7216), fully client-side: - 10 built-in theme presets (Nord, Dracula, Solarized, etc.) - Preset gallery with tag filtering and one-click apply - Custom color editor with live preview of real UI components - Custom themes saved to localStorage (no server round-trips) - Instant theme switching via style.setProperty() (no page reload) - ThemeContext gains applyPreset() and activePresetId Removed: - regex dependency from goose-server (was for deleted Rust CSS parser) - PROPOSAL-theme-tokens.md design doc - Old single-file ThemeColorEditor.tsx (replaced by directory)
Summary
This PR replaces the server-side Rust CSS parser (
theme_css.rs) with a TypeScript-first theme token system. Instead of parsing and serving CSS from the backend, theme tokens are now defined, built, and applied entirely on the client viatheme-tokens.ts.What changed
Removed (server-side)
theme_css.rs— Rust CSS variable parser/config/themeand/config/theme/variablesAPI endpointsregexdependency fromgoose-serverAdded (client-side)
ui/desktop/src/theme/theme-tokens.ts— single source of truth for all design tokens (colors, typography, borders, shadows) with light/dark variantsapplyThemeTokens()— applies tokens as CSS custom properties viastyle.setProperty()on:rootThemeColorEditordev tool component for live token editingtokenVersionstate inThemeContextto supportlocalStorageoverrides and force re-applicationRestructured
main.cssreorganized into numbered sections:@theme inline)Migrated legacy class names
bg-background-default→bg-background-primaryborder-border-default→border-border-primaryborder-border-strong→border-border-secondarybg-background-muted→bg-background-secondarytext-text-muted→text-text-secondaryaccentCSS aliases (no TSX consumers)Why
The Rust CSS parser was fragile and added unnecessary server round-trips for what is fundamentally a client concern. Moving tokens to TypeScript:
There are a few visual regressions introduced by the
main.cssrefactor that I still need to work through. The token values and/or section 4 alias mappings may need tuning.I will follow up with fixes for these before this is ready to merge.