CherryHQ/cherry-studio · error · Error
[theme-contract] renderer TypeScript source ${fileName} cann
Error message
[theme-contract] renderer TypeScript source ${fileName} cannot write shared theme variable ${disallowedWrite}; use a registered --cs-theme-* input or an owner-local --app-* variable What it means
Thrown by the theme migration contract validator when a renderer TypeScript source file calls element.style.setProperty() to write a shared theme variable that is not a registered runtime input. The validator flags setProperty calls targeting: (1) any --cs-* variable not in RUNTIME_THEME_INPUT_VARIABLES, (2) any PUBLIC_SEMANTIC_VARIABLES token, or (3) any --color-* adapter variable. The contract requires writing only through registered --cs-theme-* inputs or owner-local --app-* variables.
Source
Thrown at packages/ui/scripts/validate-migration-contract.ts:211
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`
)
}
const disallowedWrite = findTypeScriptDisallowedThemeWrite(source, fileName)
if (disallowedWrite) {
throw new Error(
`[theme-contract] renderer TypeScript source ${fileName} cannot write shared theme variable ${disallowedWrite}; use a registered --cs-theme-* input or an owner-local --app-* variable`
)
}
}
}
export async function loadMigrationContractSources(
repositoryRoot = DEFAULT_REPOSITORY_ROOT
): Promise<MigrationContractSources> {
const [migrationRegistry, legacyAliases, rendererTheme, rendererStyleEntries, rendererTypeScriptEntries] =
await Promise.all([
fs.readFile(path.join(repositoryRoot, 'packages/ui/scripts/migrations/shadcn-v2.json'), 'utf8'),
fs
.readFile(path.join(repositoryRoot, 'src/renderer/assets/styles/legacy-vars.css'), 'utf8')
.catch((error: NodeJS.ErrnoException) => {
if (error.code === 'ENOENT') return ''
throw error
}),View on GitHub (pinned to 726446b54c)
Solutions
- Change the setProperty target to the corresponding registered --cs-theme-* input variable (e.g., setProperty('--cs-theme-background', value) instead of '--background').
- If the variable is host-local (component-specific), rename it to an --app-* namespace variable.
- If the intent is to switch themes, set the runtime theme inputs (--cs-theme-*) which the contract layer translates into the semantic variables — do not write semantic variables directly.
- Confirm by running: npx tsx packages/ui/scripts/validate-migration-contract.ts.
Example fix
// before — writing a shared semantic variable directly
document.documentElement.style.setProperty('--background', newColor)
// after — write through the registered runtime input
document.documentElement.style.setProperty('--cs-theme-background', newColor)
// or use the preference system if a theme switch is intended Defensive patterns
Strategy: validation
Validate before calling
// Scan renderer .ts/.tsx files for disallowed setProperty targets before committing
import { readFileSync } from 'node:fs'
const RUNTIME_INPUTS = new Set(['--cs-theme-background', '--cs-theme-foreground' /* ... full list */])
const SEMANTIC_VARS = new Set(['--background', '--foreground' /* ... */])
function checkNoDisallowedWrite(tsPath: string): void {
const source = readFileSync(tsPath, 'utf8')
if (!source.includes('setProperty')) return
// Parse AST or use regex to find setProperty('--name', ...) calls and validate names
const matches = source.matchAll(/\.setProperty\(\s*['"`]([^'"`]+)['"`]/g)
for (const match of matches) {
const name = match[1]
if ((name.startsWith('--cs-') && !RUNTIME_INPUTS.has(name)) ||
SEMANTIC_VARS.has(name) || name.startsWith('--color-')) {
throw new Error(`${tsPath} writes disallowed variable ${name}`)
}
}
} Prevention
- When modifying theme values at runtime, always write to --cs-theme-* input variables, never directly to semantic variables.
- For component-local styling, use --app-* namespace variables that you own.
- Consult RUNTIME_THEME_INPUT_TOKENS in packages/ui/scripts/theme-contract.ts for the list of writable runtime inputs.
- Run the migration contract validator in CI to catch disallowed setProperty calls.
When it happens
Trigger: A .ts/.tsx file under src/renderer/ contains a call like document.documentElement.style.setProperty('--background', newColor) or element.style.setProperty('--color-primary', value). The validator parses the TS AST, finds CallExpression nodes where the callee is a .setProperty property access, and checks the first argument against the disallowed patterns.
Common situations: A theme-switching feature that tries to override a Shadcn semantic variable (--background, --foreground) directly instead of setting the runtime input (--cs-theme-*). Dynamic theming code that writes to adapter --color-* tokens. A migration that left old setProperty calls targeting the previous variable namespace. A component that modifies shared CSS variables at runtime, breaking the contract's single-owner rule.
Related errors
- [theme-contract] renderer TypeScript source ${fileName} cann
- [theme-contract] renderer theme must use the shared generate
- [theme-contract] renderer stylesheet ${fileName} cannot use
- [theme-contract] ${sourceName} declares invalid custom prope
- [theme-contract] ${sourceName} references invalid custom pro
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/b9ba86e7e929dbe8.
Report an issue: GitHub.