{"record":{"id":"e147468a9994aeed","repo":"CherryHQ/cherry-studio","slug":"theme-contract-product-css-declares-unregistered","errorCode":null,"errorMessage":"[theme-contract] product.css declares unregistered product variable ${name}","messagePattern":"\\[theme-contract\\] product\\.css declares unregistered product variable (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/scripts/validate-theme-contract.ts","lineNumber":413,"sourceCode":"    ['theme-input.css', sources.themeInput],\n    ['shadcn.css', sources.shadcn],\n    ['product.css', sources.product]\n  ]\n  const rootDeclarations = buildDeclarationMap(orderedSources, ':root')\n  const darkOverrides = buildDeclarationMap(orderedSources, '.dark')\n  const darkDeclarations = new Map(rootDeclarations)\n  for (const [name, declaration] of darkOverrides) darkDeclarations.set(name, declaration)\n\n  assertRequiredDeclarations('runtime theme inputs', rootDeclarations, RUNTIME_THEME_INPUT_TOKENS, '--cs-theme-')\n  assertRequiredDeclarations('Shadcn contract', rootDeclarations, SHADCN_VARIABLE_TOKENS, '--')\n  assertRequiredDeclarations('product contract', rootDeclarations, CHERRY_PRODUCT_VARIABLE_TOKENS, '--')\n\n  const productDeclarationNames = new Set(\n    extractDeclarations(sources.product, 'product.css').map((declaration) => declaration.name)\n  )\n  for (const name of productDeclarationNames) {\n    if (!productVariableNames.has(name)) {\n      throw new Error(`[theme-contract] product.css declares unregistered product variable ${name}`)\n    }\n  }\n\n  assertReferencesResolve('light', rootDeclarations)\n  assertReferencesResolve('dark', darkDeclarations)\n  assertNoCycles('light', rootDeclarations)\n  assertNoCycles('dark', darkDeclarations)\n}\n\nexport async function loadThemeContractSources(stylesDir = DEFAULT_STYLES_DIR): Promise<ThemeContractSources> {\n  const tokensDir = path.join(stylesDir, 'tokens')\n  const [\n    variableCatalog,\n    contractEntry,\n    tokensEntry,\n    tokensIndex,\n    themeInput,\n    primitiveColors,","sourceCodeStart":395,"sourceCodeEnd":431,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/packages/ui/scripts/validate-theme-contract.ts#L395-L431","documentation":"Thrown by validateThemeContractSources when product.css declares a --<name> variable that is not in CHERRY_PRODUCT_VARIABLE_TOKENS. The product contract is a closed set; only registered product variables may be declared in product.css. This catches invented product roles, typos, and declarations that belong in another layer.","triggerScenarios":"Adding `:root { --chat-bubble: red; }` to product.css without registering 'chat-bubble' in CHERRY_PRODUCT_VARIABLE_TOKENS; pasting a foundation --cs-* declaration into product.css.","commonSituations":"Adding a new product role and forgetting the registry/catalog half; leftover experimental declarations; rebasing across a product-token rename.","solutions":["If it is a genuine new product variable, register it in CHERRY_PRODUCT_VARIABLE_TOKENS (and CHERRY_PRODUCT_COLOR_TOKENS if it needs a Tailwind utility, plus document it in variable-catalog.md), then keep the declaration in product.css.","If it belongs to another layer (foundation --cs-*, runtime --cs-theme-*, or Shadcn), move it to the correct file.","Re-run `pnpm --filter @cherrystudio/ui theme:check`."],"exampleFix":"// before (product.css)\n:root { --chat-bubble: red; }\n// 'chat-bubble' is not registered\n// after — register in scripts/theme-contract.ts\nexport const CHERRY_PRODUCT_VARIABLE_TOKENS = ['chat-bubble', ...] as const\n// and add `--chat-bubble` to variable-catalog.md","handlingStrategy":"validation","validationCode":"// Every variable declared in product.css must be a registered product variable.\nconst allowed = new Set(CHERRY_PRODUCT_VARIABLE_TOKENS.map((t) => `--${t}`))\nconst offenders = [...sources.product.matchAll(/(?:^|[;{])\\s*(--[a-z0-9-]+)\\s*:/g)].map((m) => m[1]).filter((n) => !allowed.has(n))\nif (offenders.length) throw new Error(`product.css declares unregistered vars: ${offenders.join(', ')}`)","typeGuard":null,"tryCatchPattern":"try {\n  validateThemeContractSources(sources)\n} catch (error) {\n  if (error instanceof Error && /product\\.css declares unregistered product variable/.test(error.message)) {\n    console.error(error.message)\n    process.exitCode = 1\n    return\n  }\n  throw error\n}","preventionTips":["Only CHERRY_PRODUCT_VARIABLE_TOKENS may be declared in product.css.","Adding a product variable means updating the registry, the CSS, the Tailwind color list (if needed), and variable-catalog.md together.","Run theme:check after editing product.css or the product registry."],"tags":["theme-contract","css","build","validation","registry"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}