Skip to content

refactor: MCP-compliant theme tokens and CSS class rename#7275

Merged
zanesq merged 8 commits intomainfrom
theme-tokens
Feb 23, 2026
Merged

refactor: MCP-compliant theme tokens and CSS class rename#7275
zanesq merged 8 commits intomainfrom
theme-tokens

Conversation

@aharvard
Copy link
Collaborator

@aharvard aharvard commented Feb 17, 2026

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 against McpUiStyleVariableKey
  • applyThemeTokens() sets CSS custom properties via style.setProperty() — instant, no server round-trip. Optionally accepts a theme argument; defaults to calling getResolvedTheme() internally.
  • getResolvedTheme() — centralized helper that reads localStorage / system preference to determine the active theme, replacing duplicated logic in renderer.tsx and ThemeContext.tsx
  • buildMcpHostStyles() — generates light-dark() format variables for MCP apps, plus css.fonts with @font-face rules for Cash Sans so sandboxed iframes can load host fonts
  • main.css restructured into 5 numbered sections with @theme inline registration
  • ThemeContext simplified to core infrastructure: userThemePreference, resolvedTheme, and setUserThemePreference

Commit 2: CSS class rename (~130 TSX files)

  • Rename 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.)

Commit 3: Fix user message bubble contrast

  • 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
  • Override prose typography styles with !text-inherit so dark:prose-invert cannot clobber bubble text color

Key decisions

  • Zero server endpoints — removed the Rust CSS parser (theme_css.rs) and all server theme endpoints from the initial PR
  • No new Rust dependencies — the regex crate that was added for the Rust parser is not included
  • TypeScript-first — all theming logic lives client-side

Verification

  • cargo build -p goose-server
  • cargo fmt -- --check
  • cargo clippy --all-targets -- -D warnings
  • npx tsc --noEmit (zero errors)
  • eslint (zero new errors, 1 pre-existing warning in McpAppRenderer)

Related

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-defaultbg-background-primary, text-text-mutedtext-text-secondary), and (3) fixing user message bubble contrast by using inverted color schemes.

Changes:

  • New TypeScript theme token system with applyThemeTokens(), buildMcpHostStyles(), and getResolvedTheme() helpers
  • Systematic CSS class renames across ~130 TSX files to align with MCP naming conventions
  • User message bubble contrast improvements using bg-text-primary and text-background-primary with !text-inherit overrides

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

Copilot AI review requested due to automatic review settings February 18, 2026 15:37
@aharvard
Copy link
Collaborator Author

@DOsinga & @zanesq, ready for review!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI review requested due to automatic review settings February 20, 2026 20:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 137 out of 137 changed files in this pull request and generated 4 comments.

Copilot AI review requested due to automatic review settings February 20, 2026 21:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 137 out of 137 changed files in this pull request and generated 3 comments.

Comment on lines 284 to 288
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})`;
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will follow up on this later.

--shadow-default: var(--shadow-default);
}
/* ═══════════════════════════════════════════════════════════════════════════
7. BASE LAYER + FONTS + ANIMATIONS + COMPONENTS
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)',
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).
Copilot AI review requested due to automatic review settings February 23, 2026 16:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 137 out of 137 changed files in this pull request and generated no new comments.

@aharvard aharvard added this pull request to the merge queue Feb 23, 2026
@zanesq zanesq removed this pull request from the merge queue due to a manual request Feb 23, 2026
@zanesq zanesq merged commit c73bad9 into main Feb 23, 2026
25 checks passed
@zanesq zanesq deleted the theme-tokens branch February 23, 2026 16:41
zanesq added a commit that referenced this pull request Feb 23, 2026
…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
michaelneale added a commit that referenced this pull request Feb 23, 2026
…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)
  ...
lifeizhou-ap added a commit that referenced this pull request Feb 24, 2026
* 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)
lifeizhou-ap added a commit that referenced this pull request Feb 24, 2026
* 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)
  ...
aharvard added a commit that referenced this pull request Feb 24, 2026
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants