CherryHQ/cherry-studio · error · Error

[theme-contract] renderer theme must use the shared generate

Error message

[theme-contract] renderer theme must use the shared generated Tailwind adapter

What it means

Thrown by the theme migration contract validator when the renderer theme entry (src/renderer/assets/styles/tailwind.css) contains a @theme { } or @theme inline { } block. The contract requires all renderer themes to consume the shared generated Tailwind adapter rather than declaring their own @theme blocks, ensuring a single source of truth for Tailwind theme tokens. This is a CI/build-time validation, not a runtime error.

Source

Thrown at packages/ui/scripts/validate-migration-contract.ts:187

      throw new Error(`[theme-contract] migration ${rule.source} points outside the canonical contract: ${rule.target}`)
    }
  }

  if (sources.legacyAliases.trim() !== '') {
    throw new Error('[theme-contract] legacy compatibility layer must remain removed')
  }

  const rendererTheme = stripComments(sources.rendererTheme)
  if (rendererTheme.includes('legacy-vars.css')) {
    throw new Error('[theme-contract] renderer theme cannot import the removed legacy compatibility layer')
  }
  if (rendererTheme.includes('--app-')) {
    throw new Error(
      '[theme-contract] renderer theme entry cannot own --app-* variables; keep host-local values in a dedicated stylesheet'
    )
  }
  if (/@theme(?:\s+inline)?\s*\{/.test(rendererTheme)) {
    throw new Error('[theme-contract] renderer theme must use the shared generated Tailwind adapter')
  }

  for (const [fileName, source] of Object.entries(sources.rendererStyles)) {
    const adapterVariable = stripComments(source).match(TAILWIND_ADAPTER_VARIABLE_PATTERN)?.[0]

    if (adapterVariable) {
      throw new Error(
        `[theme-contract] renderer stylesheet ${fileName} cannot use Tailwind adapter variable ${adapterVariable}; use runtime semantic variables directly`
      )
    }
  }

  for (const [fileName, source] of Object.entries(sources.rendererTypeScriptSources)) {
    const adapterVariable = source.includes('--color-') ? findTypeScriptAdapterVariable(source, fileName) : undefined

    if (adapterVariable) {
      throw new Error(
        `[theme-contract] renderer TypeScript source ${fileName} cannot use Tailwind adapter variable ${adapterVariable}; use runtime semantic variables or Tailwind utilities`

View on GitHub (pinned to 726446b54c)

Solutions

  1. Remove the @theme / @theme inline block from the renderer theme entry (src/renderer/assets/styles/tailwind.css).
  2. If you need custom theme tokens, add them to the shared theme system: declare them in packages/ui/src/styles/ (e.g., product.css or a token file) following the contract, not in the renderer entry.
  3. If the block was added inadvertently (merge artifact), revert that section to use the shared Tailwind adapter import.
  4. Run the validator locally: npx tsx packages/ui/scripts/validate-migration-contract.ts to confirm the fix.

Example fix

// before — src/renderer/assets/styles/tailwind.css
@import "tailwindcss";
@theme {
  --color-my-brand: #ff0000;
}

// after — remove @theme, rely on shared adapter
@import "tailwindcss";
@import "@cherrystudio/ui/styles";
/* custom tokens go in packages/ui/src/styles/product.css */
Defensive patterns

Strategy: validation

Validate before calling

// Run the migration contract validator before committing theme changes
// In package.json scripts or as a pre-commit hook:
// "theme:migration-check": "tsx packages/ui/scripts/validate-migration-contract.ts"
// Then: pnpm theme:migration-check
// The validator scans src/renderer/assets/styles/tailwind.css for @theme blocks

Type guard

// Regex check to run before committing renderer theme changes
import { readFileSync } from 'node:fs'

function checkNoThemeBlock(filePath: string): void {
  const source = readFileSync(filePath, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
  if (/@theme(?:\s+inline)?\s*\{/.test(source)) {
    throw new Error(`${filePath} contains a @theme block — use the shared Tailwind adapter instead`)
  }
}

checkNoThemeBlock('src/renderer/assets/styles/tailwind.css')

Prevention

When it happens

Trigger: A developer edits src/renderer/assets/styles/tailwind.css and adds a @theme { ... } or @theme inline { ... } directive to define custom Tailwind theme values locally. The validator scans the file (after stripping comments) with the regex /@theme(?:\s+inline)?\s*\{/ and throws on match.

Common situations: A developer unfamiliar with the architecture contract tries to add custom design tokens via a local @theme block instead of the shared theme system. Copy-pasting a Tailwind CSS v4 tutorial that uses @theme. Attempting to override a theme value locally without understanding the centralized adapter requirement. A merge that reintroduces a previously removed @theme block.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/6eec0354502e389e. Report an issue: GitHub.