{"record":{"id":"b9ba86e7e929dbe8","repo":"CherryHQ/cherry-studio","slug":"theme-contract-renderer-typescript-source-file-b9ba86","errorCode":null,"errorMessage":"[theme-contract] renderer TypeScript source ${fileName} cannot write shared theme variable ${disallowedWrite}; use a registered --cs-theme-* input or an owner-local --app-* variable","messagePattern":"\\[theme-contract\\] renderer TypeScript source (.+?) cannot write shared theme variable (.+?); use a registered --cs-theme-\\* input or an owner-local --app-\\* variable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/scripts/validate-migration-contract.ts","lineNumber":211,"sourceCode":"    if (adapterVariable) {\n      throw new Error(\n        `[theme-contract] renderer stylesheet ${fileName} cannot use Tailwind adapter variable ${adapterVariable}; use runtime semantic variables directly`\n      )\n    }\n  }\n\n  for (const [fileName, source] of Object.entries(sources.rendererTypeScriptSources)) {\n    const adapterVariable = source.includes('--color-') ? findTypeScriptAdapterVariable(source, fileName) : undefined\n\n    if (adapterVariable) {\n      throw new Error(\n        `[theme-contract] renderer TypeScript source ${fileName} cannot use Tailwind adapter variable ${adapterVariable}; use runtime semantic variables or Tailwind utilities`\n      )\n    }\n\n    const disallowedWrite = findTypeScriptDisallowedThemeWrite(source, fileName)\n    if (disallowedWrite) {\n      throw new Error(\n        `[theme-contract] renderer TypeScript source ${fileName} cannot write shared theme variable ${disallowedWrite}; use a registered --cs-theme-* input or an owner-local --app-* variable`\n      )\n    }\n  }\n}\n\nexport async function loadMigrationContractSources(\n  repositoryRoot = DEFAULT_REPOSITORY_ROOT\n): Promise<MigrationContractSources> {\n  const [migrationRegistry, legacyAliases, rendererTheme, rendererStyleEntries, rendererTypeScriptEntries] =\n    await Promise.all([\n      fs.readFile(path.join(repositoryRoot, 'packages/ui/scripts/migrations/shadcn-v2.json'), 'utf8'),\n      fs\n        .readFile(path.join(repositoryRoot, 'src/renderer/assets/styles/legacy-vars.css'), 'utf8')\n        .catch((error: NodeJS.ErrnoException) => {\n          if (error.code === 'ENOENT') return ''\n          throw error\n        }),","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/packages/ui/scripts/validate-migration-contract.ts#L193-L229","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — writing a shared semantic variable directly\ndocument.documentElement.style.setProperty('--background', newColor)\n\n// after — write through the registered runtime input\ndocument.documentElement.style.setProperty('--cs-theme-background', newColor)\n// or use the preference system if a theme switch is intended","handlingStrategy":"validation","validationCode":"// Scan renderer .ts/.tsx files for disallowed setProperty targets before committing\nimport { readFileSync } from 'node:fs'\n\nconst RUNTIME_INPUTS = new Set(['--cs-theme-background', '--cs-theme-foreground' /* ... full list */])\nconst SEMANTIC_VARS = new Set(['--background', '--foreground' /* ... */])\n\nfunction checkNoDisallowedWrite(tsPath: string): void {\n  const source = readFileSync(tsPath, 'utf8')\n  if (!source.includes('setProperty')) return\n  // Parse AST or use regex to find setProperty('--name', ...) calls and validate names\n  const matches = source.matchAll(/\\.setProperty\\(\\s*['\"`]([^'\"`]+)['\"`]/g)\n  for (const match of matches) {\n    const name = match[1]\n    if ((name.startsWith('--cs-') && !RUNTIME_INPUTS.has(name)) ||\n        SEMANTIC_VARS.has(name) || name.startsWith('--color-')) {\n      throw new Error(`${tsPath} writes disallowed variable ${name}`)\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["theme-contract","build-validation","typescript","css-variables","runtime-theming"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}