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
- Remove the @theme / @theme inline block from the renderer theme entry (src/renderer/assets/styles/tailwind.css).
- 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.
- If the block was added inadvertently (merge artifact), revert that section to use the shared Tailwind adapter import.
- 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
- Never add @theme or @theme inline blocks to renderer CSS — all theme tokens come from the shared adapter.
- Add the contract validator to your pre-commit hooks or CI pipeline.
- When onboarding new developers, document the theme architecture: renderer entries consume the shared adapter, they don't declare tokens.
- If you need custom design tokens, add them to packages/ui/src/styles/ following the contract, not to renderer CSS.
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
- [theme-contract] renderer stylesheet ${fileName} cannot use
- [theme-contract] renderer TypeScript source ${fileName} cann
- [theme-contract] ${sourceName} declares invalid custom prope
- [theme-contract] ${sourceName} references invalid custom pro
- [theme-contract] unsupported @import syntax: ${importValue}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/6eec0354502e389e.
Report an issue: GitHub.