refactor: MCP-compliant theme tokens and CSS class rename#7275
Conversation
5dc34ec to
90bc11a
Compare
There was a problem hiding this comment.
Pull request overview
This PR establishes a TypeScript-first theme token system for both the Goose desktop UI and MCP apps, eliminating the need for server-side CSS parsing. It includes three main commits: (1) creating the theme token infrastructure with theme-tokens.ts and related helpers, (2) renaming ~130 legacy CSS classes to MCP-compliant equivalents (e.g., bg-background-default → bg-background-primary, text-text-muted → text-text-secondary), and (3) fixing user message bubble contrast by using inverted color schemes.
Changes:
- New TypeScript theme token system with
applyThemeTokens(),buildMcpHostStyles(), andgetResolvedTheme()helpers - Systematic CSS class renames across ~130 TSX files to align with MCP naming conventions
- User message bubble contrast improvements using
bg-text-primaryandtext-background-primarywith!text-inheritoverrides
Reviewed changes
Copilot reviewed 137 out of 137 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| ui/desktop/src/renderer.tsx | Early theme token application before first paint |
| ui/desktop/src/preload.ts | Added tokensUpdated optional property to theme change broadcast |
| ui/desktop/src/contexts/ThemeContext.tsx | Integrated theme token functions and added mcpHostStyles to context |
| ui/desktop/src/components/UserMessage.tsx | Fixed bubble contrast with inverted colors and !text-inherit prose overrides |
| ~130 component files | Systematic CSS class renames from legacy to MCP-compliant naming |
ce7b889 to
74bbb84
Compare
6d4519b to
6263672
Compare
| for (const key of Object.keys(lightTokens) as McpUiStyleVariableKey[]) { | ||
| const light = lightTokens[key]; | ||
| const dark = darkTokens[key]; | ||
| variables[key] = light === dark ? light : `light-dark(${light}, ${dark})`; | ||
| } |
There was a problem hiding this comment.
buildMcpHostStyles() wraps any differing token values in light-dark(...), but several non-color tokens (e.g., shadows) differ between lightTokens and darkTokens, and light-dark() is only valid for color values; this will make those variables invalid when used in properties like box-shadow. Consider limiting light-dark() generation to color keys (e.g., --color-*, --color-ring-*) and keeping non-color tokens identical for host styles (or exposing theme-specific non-color tokens via another mechanism).
There was a problem hiding this comment.
will follow up on this later.
ui/desktop/src/styles/main.css
Outdated
| --shadow-default: var(--shadow-default); | ||
| } | ||
| /* ═══════════════════════════════════════════════════════════════════════════ | ||
| 7. BASE LAYER + FONTS + ANIMATIONS + COMPONENTS |
There was a problem hiding this comment.
what happened to section 6?
| '--shadow-md': '0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -2px rgba(0, 0, 0, 0.2)', | ||
| '--shadow-lg': | ||
| '0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -4px rgba(0, 0, 0, 0.2)', | ||
| }; |
There was a problem hiding this comment.
I think we can elminate some duplication here and get slightly better type safety with something like:
const baseTokens = { /* fonts, radii, shadows / } as const;
const lightColors = { / colors only / } as const;
const darkColors = { / colors only */ } as const;
export const lightTokens = { ...baseTokens, ...lightColors } satisfies ThemeTokens;
export const darkTokens = { ...baseTokens, ...darkColors } satisfies ThemeTokens;
There was a problem hiding this comment.
Agreed, and get valid CSS too! missed that... whoops
…n.css - Add theme-tokens.ts as single source of truth for all MCP spec token values - Apply tokens to :root via style.setProperty() before first paint (renderer.tsx) - Remove duplicate token values from main.css :root/.dark blocks - main.css now only registers variable names for Tailwind (@theme inline) and defines app-specific aliases (sidebar, search highlight, legacy) - Add buildMcpHostStyles() to generate light-dark() format for MCP apps - Update ThemeContext with refreshTokens, tokenVersion, localStorage override - Update preload and suspense-loader to use MCP-compliant variable names
Rename ~130 TSX files from legacy class names (bg-background-default, border-default, text-muted, etc.) to MCP-compliant equivalents (bg-background-primary, border-border-primary, text-text-secondary, etc.) This aligns the Goose desktop UI with the MCP theming specification, enabling consistent styling across both the host app and MCP app iframes.
Use bg-text-primary and text-background-primary to naturally invert the color scheme inside chat bubbles, ensuring readable text in both light and dark modes.
Section headers jumped from 5 to 7, skipping 6. This was a typo introduced when the file was restructured into numbered sections.
- Extract shared tokens (fonts, radii, border-width) into baseTokens to eliminate duplication between light and dark theme definitions. - Use type-safe Pick/Exclude to enforce the split at compile time. - Restrict light-dark() in buildMcpHostStyles() to --color-* keys only, since light-dark() is a CSS color function and produces invalid values for non-color properties like box-shadow. - Always wrap color keys in light-dark() for consistency, even when both theme values are identical (e.g. transparent, #878787).
9511d70 to
23b36f7
Compare
…oviders * 'main' of github.com:block/goose: New navigation settings layout options and styling (#6645) refactor: MCP-compliant theme tokens and CSS class rename (#7275) Redirect llama.cpp logs through tracing to avoid polluting CLI stdout/stderr (#7434) refactor: change open recipe in new window to pass recipe id (#7392) fix: handle truncated tool calls that break conversation alternation (#7424) streamline some github actions (#7430) Enable bedrock prompt cache (#6710) fix: use BEGIN IMMEDIATE to prevent SQLite deadlocks (#7429) Display working dir (#7419) dev: add cmake to hermitized env (#7399) refactor: remove allows_unlisted_models flag, always allow custom model entry (#7255) feat: expose context window utilization to agent via MOIM (#7418) Small model naming (#7394) chore(deps): bump ajv in /documentation (#7416) doc: groq models (#7404) Client settings (#7381) Fix settings tabs getting cut off in narrow windows (#7379) # Conflicts: # ui/desktop/src/components/settings/dictation/DictationSettings.tsx
…xt-edit * origin/main: (35 commits) docs: generate manpages (#7443) Blog/goose v1 25 0 release (#7433) fix: detect truncated LLM responses in apps extension (#7354) fix: removed unnecessary version for goose acp macro dependency (#7428) add flag to hide select voice providers (#7406) New navigation settings layout options and styling (#6645) refactor: MCP-compliant theme tokens and CSS class rename (#7275) Redirect llama.cpp logs through tracing to avoid polluting CLI stdout/stderr (#7434) refactor: change open recipe in new window to pass recipe id (#7392) fix: handle truncated tool calls that break conversation alternation (#7424) streamline some github actions (#7430) Enable bedrock prompt cache (#6710) fix: use BEGIN IMMEDIATE to prevent SQLite deadlocks (#7429) Display working dir (#7419) dev: add cmake to hermitized env (#7399) refactor: remove allows_unlisted_models flag, always allow custom model entry (#7255) feat: expose context window utilization to agent via MOIM (#7418) Small model naming (#7394) chore(deps): bump ajv in /documentation (#7416) doc: groq models (#7404) ...
* main: Simplified custom model flow with canonical models (#6934) feat: simplify the text editor to be more like pi (#7426) docs: add YouTube short embed to Neighborhood extension tutorial (#7456) fix: flake.nix build failure and deprecation warning (#7408) feat(claude-code): add permission prompt routing for approve mode (#7420) docs: generate manpages (#7443) Blog/goose v1 25 0 release (#7433) fix: detect truncated LLM responses in apps extension (#7354) fix: removed unnecessary version for goose acp macro dependency (#7428) add flag to hide select voice providers (#7406) New navigation settings layout options and styling (#6645) refactor: MCP-compliant theme tokens and CSS class rename (#7275) Redirect llama.cpp logs through tracing to avoid polluting CLI stdout/stderr (#7434) refactor: change open recipe in new window to pass recipe id (#7392) fix: handle truncated tool calls that break conversation alternation (#7424) streamline some github actions (#7430) Enable bedrock prompt cache (#6710) fix: use BEGIN IMMEDIATE to prevent SQLite deadlocks (#7429) Display working dir (#7419)
* main: (171 commits) fix: TLDR CLI tab in Neighborhood MCP docs (#7461) fix(summon): restore skill supporting files and directory path in load output (#7457) Simplified custom model flow with canonical models (#6934) feat: simplify the text editor to be more like pi (#7426) docs: add YouTube short embed to Neighborhood extension tutorial (#7456) fix: flake.nix build failure and deprecation warning (#7408) feat(claude-code): add permission prompt routing for approve mode (#7420) docs: generate manpages (#7443) Blog/goose v1 25 0 release (#7433) fix: detect truncated LLM responses in apps extension (#7354) fix: removed unnecessary version for goose acp macro dependency (#7428) add flag to hide select voice providers (#7406) New navigation settings layout options and styling (#6645) refactor: MCP-compliant theme tokens and CSS class rename (#7275) Redirect llama.cpp logs through tracing to avoid polluting CLI stdout/stderr (#7434) refactor: change open recipe in new window to pass recipe id (#7392) fix: handle truncated tool calls that break conversation alternation (#7424) streamline some github actions (#7430) Enable bedrock prompt cache (#6710) fix: use BEGIN IMMEDIATE to prevent SQLite deadlocks (#7429) ...
* origin/main: (49 commits) add flag to hide select voice providers (#7406) New navigation settings layout options and styling (#6645) refactor: MCP-compliant theme tokens and CSS class rename (#7275) Redirect llama.cpp logs through tracing to avoid polluting CLI stdout/stderr (#7434) refactor: change open recipe in new window to pass recipe id (#7392) fix: handle truncated tool calls that break conversation alternation (#7424) streamline some github actions (#7430) Enable bedrock prompt cache (#6710) fix: use BEGIN IMMEDIATE to prevent SQLite deadlocks (#7429) Display working dir (#7419) dev: add cmake to hermitized env (#7399) refactor: remove allows_unlisted_models flag, always allow custom model entry (#7255) feat: expose context window utilization to agent via MOIM (#7418) Small model naming (#7394) chore(deps): bump ajv in /documentation (#7416) doc: groq models (#7404) Client settings (#7381) Fix settings tabs getting cut off in narrow windows (#7379) docs: voice dictation updates (#7396) [docs] Add Excalidraw MCP App Tutorial (#7401) ... # Conflicts: # ui/desktop/src/components/McpApps/McpAppRenderer.tsx
Summary
Establishes the theming infrastructure for both the Goose desktop UI and MCP apps.
What's included
Commit 1: TypeScript-first theme token system
theme-tokens.ts— single source of truth for all MCP spec tokens, typed againstMcpUiStyleVariableKeyapplyThemeTokens()sets CSS custom properties viastyle.setProperty()— instant, no server round-trip. Optionally accepts a theme argument; defaults to callinggetResolvedTheme()internally.getResolvedTheme()— centralized helper that reads localStorage / system preference to determine the active theme, replacing duplicated logic inrenderer.tsxandThemeContext.tsxbuildMcpHostStyles()— generateslight-dark()format variables for MCP apps, pluscss.fontswith@font-facerules for Cash Sans so sandboxed iframes can load host fontsmain.cssrestructured into 5 numbered sections with@theme inlineregistrationThemeContextsimplified to core infrastructure:userThemePreference,resolvedTheme, andsetUserThemePreferenceCommit 2: CSS class rename (~130 TSX files)
bg-background-default,border-default,text-muted, etc.) to MCP-compliant equivalents (bg-background-primary,border-border-primary,text-text-secondary, etc.)Commit 3: Fix user message bubble contrast
bg-text-primaryandtext-background-primaryto naturally invert the color scheme inside chat bubbles, ensuring readable text in both light and dark modes!text-inheritsodark:prose-invertcannot clobber bubble text colorKey decisions
theme_css.rs) and all server theme endpoints from the initial PRregexcrate that was added for the Rust parser is not includedVerification
cargo build -p goose-servercargo fmt -- --checkcargo clippy --all-targets -- -D warningsnpx tsc --noEmit(zero errors)eslint(zero new errors, 1 pre-existing warning in McpAppRenderer)Related