{"record":{"id":"fe27ec5da3012895","repo":"CherryHQ/cherry-studio","slug":"theme-contract-tailwind-product-color-token-i","errorCode":null,"errorMessage":"[theme-contract] Tailwind product color ${token} is missing from the product contract","messagePattern":"\\[theme-contract\\] Tailwind product color (.+?) is missing from the product contract","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/scripts/validate-theme-contract.ts","lineNumber":330,"sourceCode":"  assertUnique('compatibility semantic colors', COMPATIBILITY_SEMANTIC_COLOR_TOKENS)\n  assertUnique('compatibility status colors', COMPATIBILITY_STATUS_COLOR_TOKENS)\n  assertUnique('compatibility colors', COMPATIBILITY_COLOR_TOKENS)\n\n  const productVariables = new Set<string>(CHERRY_PRODUCT_VARIABLE_TOKENS)\n  const shadcnVariables = new Set<string>(SHADCN_VARIABLE_TOKENS)\n  const shadcnVariableNames = new Set<string>(SHADCN_VARIABLE_TOKENS.map((token) => `--${token}`))\n  const productVariableNames = new Set<string>(CHERRY_PRODUCT_VARIABLE_TOKENS.map((token) => `--${token}`))\n  const canonicalColors = new Set<string>([...SHADCN_COLOR_TOKENS, ...CHERRY_PRODUCT_COLOR_TOKENS])\n\n  for (const token of CHERRY_PRODUCT_VARIABLE_TOKENS) {\n    if (shadcnVariables.has(token)) {\n      throw new Error(`[theme-contract] product variable ${token} overlaps the official Shadcn contract`)\n    }\n  }\n\n  for (const token of CHERRY_PRODUCT_COLOR_TOKENS) {\n    if (!productVariables.has(token)) {\n      throw new Error(`[theme-contract] Tailwind product color ${token} is missing from the product contract`)\n    }\n  }\n  for (const token of COMPATIBILITY_COLOR_TOKENS) {\n    if (canonicalColors.has(token)) {\n      throw new Error(`[theme-contract] compatibility color ${token} overlaps the canonical color contract`)\n    }\n  }\n  assertCompatibilityTokensDeclared(\n    'compatibility semantic colors',\n    COMPATIBILITY_SEMANTIC_COLOR_TOKENS,\n    sources.providerColors,\n    'tokens/colors/providers.css'\n  )\n  assertCompatibilityTokensDeclared(\n    'compatibility status colors',\n    COMPATIBILITY_STATUS_COLOR_TOKENS,\n    sources.statusLegacyColors,\n    'tokens/colors/status-legacy.css'","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/packages/ui/scripts/validate-theme-contract.ts#L312-L348","documentation":"Thrown by validateThemeContractSources when a token in CHERRY_PRODUCT_COLOR_TOKENS (the Tailwind-exposed color surface) is missing from CHERRY_PRODUCT_VARIABLE_TOKENS (the CSS contract). Every Tailwind color utility must be backed by a declared product CSS variable so the utility and the contract cannot drift.","triggerScenarios":"Adding 'new-role' to CHERRY_PRODUCT_COLOR_TOKENS in scripts/theme-contract.ts to generate a Tailwind utility, without also adding 'new-role' to CHERRY_PRODUCT_VARIABLE_TOKENS and declaring `--new-role` in product.css.","commonSituations":"Exposing a new Tailwind color utility and forgetting the contract half; trimming the variable list but leaving the color list.","solutions":["Add the token to CHERRY_PRODUCT_VARIABLE_TOKENS in scripts/theme-contract.ts AND declare `--<token>` in product.css.","If the Tailwind utility is not needed, remove the token from CHERRY_PRODUCT_COLOR_TOKENS instead.","Re-run `pnpm --filter @cherrystudio/ui theme:check`."],"exampleFix":"// before — theme-contract.ts\nexport const CHERRY_PRODUCT_COLOR_TOKENS = ['new-role', ...] as const\n// CHERRY_PRODUCT_VARIABLE_TOKENS does not include 'new-role'\n// after\nexport const CHERRY_PRODUCT_VARIABLE_TOKENS = ['new-role', ...] as const\n// and in product.css: :root { --new-role: var(--cs-...); }","handlingStrategy":"validation","validationCode":"// Every Tailwind color token must be a declared product variable.\nconst productVars = new Set(CHERRY_PRODUCT_VARIABLE_TOKENS)\nconst missing = CHERRY_PRODUCT_COLOR_TOKENS.filter((t) => !productVars.has(t))\nif (missing.length) throw new Error(`color tokens missing from product contract: ${missing.join(', ')}`)","typeGuard":null,"tryCatchPattern":"try {\n  validateThemeContractSources(sources)\n} catch (error) {\n  if (error instanceof Error && /Tailwind product color .* is missing from the product contract/.test(error.message)) {\n    console.error(error.message)\n    process.exitCode = 1\n    return\n  }\n  throw error\n}","preventionTips":["Adding a token to CHERRY_PRODUCT_COLOR_TOKENS requires the same token in CHERRY_PRODUCT_VARIABLE_TOKENS and a `--<token>` declaration in product.css.","Treat the two lists as a pair; only add a Tailwind utility when the CSS variable exists.","Run theme:check after editing either list."],"tags":["theme-contract","css","build","validation","tailwind","registry"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}