{"record":{"id":"8e3b5669ef1e8ae4","repo":"CherryHQ/cherry-studio","slug":"theme-contract-label-pair-surface-fore","errorCode":null,"errorMessage":"[theme-contract] ${label} pair ${surface} / ${foreground} is outside its public contract","messagePattern":"\\[theme-contract\\] (.+?) pair (.+?) / (.+?) is outside its public contract","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/scripts/validate-theme-contract.ts","lineNumber":124,"sourceCode":"function assertUnique(label: string, values: readonly string[]): void {\n  if (new Set(values).size !== values.length) {\n    throw new Error(`[theme-contract] ${label} contains duplicate names`)\n  }\n}\n\nfunction assertSurfacePairs(\n  label: string,\n  pairs: ReadonlyArray<readonly [surface: string, foreground: string]>,\n  variableNames: Set<string>\n): void {\n  const surfaces = new Set<string>()\n\n  for (const [surface, foreground] of pairs) {\n    if (surface === foreground || surfaces.has(surface)) {\n      throw new Error(`[theme-contract] ${label} has an invalid or duplicate surface pair for ${surface}`)\n    }\n    if (!variableNames.has(surface) || !variableNames.has(foreground)) {\n      throw new Error(`[theme-contract] ${label} pair ${surface} / ${foreground} is outside its public contract`)\n    }\n    surfaces.add(surface)\n  }\n}\n\nfunction assertExactImports(label: string, source: string, expected: readonly string[]): void {\n  const actual = extractImports(source)\n\n  if (actual.length !== expected.length || actual.some((entry, index) => entry !== expected[index])) {\n    throw new Error(`[theme-contract] ${label} imports must be exactly: ${expected.join(' -> ')}`)\n  }\n}\n\nfunction buildDeclarationMap(entries: SourceEntry[], selector: ':root' | '.dark'): Map<string, Declaration> {\n  const declarations = new Map<string, Declaration>()\n\n  for (const [sourceName, source] of entries) {\n    for (const declaration of extractModeDeclarations(source, sourceName, selector)) {","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/packages/ui/scripts/validate-theme-contract.ts#L106-L142","documentation":"Thrown by the theme contract validator when a surface pair references a variable name that is not in the corresponding variable-names set. The assertSurfacePairs function checks that both the surface and foreground tokens of each pair exist in the provided variableNames Set (which is built from the SHADCN_VARIABLE_TOKENS or CHERRY_PRODUCT_VARIABLE_TOKENS array). This ensures every surface pair is backed by a declared variable in the public contract.","triggerScenarios":"A surface pair lists a token name (e.g., 'card-foreground') that is not present in the corresponding variable token array (SHADCN_VARIABLE_TOKENS or CHERRY_PRODUCT_VARIABLE_TOKENS). The check fires when either the surface or foreground of a pair is missing from the variableNames set.","commonSituations":"A developer adds a surface pair referencing a new token but forgot to add the token to the variable token array. A token was renamed in the variable array but the surface pair wasn't updated. A surface pair was copied from another contract system with different variable names. The foreground token name has a typo.","solutions":["Check the surface pair identified in the error message and verify both tokens exist in the corresponding variable token array (SHADCN_VARIABLE_TOKENS or CHERRY_PRODUCT_VARIABLE_TOKENS) in packages/ui/scripts/theme-contract.ts.","If a token is missing from the array, add it and ensure it's declared in the corresponding CSS file (shadcn.css or product.css).","If the pair references a non-existent token, correct the token name to match an existing one.","Run the validator to confirm: npx tsx packages/ui/scripts/validate-theme-contract.ts."],"exampleFix":"// before — surface pair references undeclared variable\nexport const SHADCN_VARIABLE_TOKENS = [\n  'card', 'card-foreground', 'popover'\n  // 'popover-foreground' missing\n] as const\nexport const SHADCN_SURFACE_PAIRS = [\n  ['card', 'card-foreground'],\n  ['popover', 'popover-foreground']  // ← 'popover-foreground' not in variable list\n] as const\n\n// after — add the missing variable\nexport const SHADCN_VARIABLE_TOKENS = [\n  'card', 'card-foreground', 'popover', 'popover-foreground'\n] as const\nexport const SHADCN_SURFACE_PAIRS = [\n  ['card', 'card-foreground'],\n  ['popover', 'popover-foreground']\n] as const","handlingStrategy":"validation","validationCode":"// Check that all surface pair tokens exist in the variable token array before committing\nimport { SHADCN_SURFACE_PAIRS, SHADCN_VARIABLE_TOKENS } from './theme-contract'\n\nconst vars = new Set(SHADCN_VARIABLE_TOKENS)\nfor (const [surface, foreground] of SHADCN_SURFACE_PAIRS) {\n  if (!vars.has(surface)) throw new Error(`Surface ${surface} missing from variable tokens`)\n  if (!vars.has(foreground)) throw new Error(`Foreground ${foreground} missing from variable tokens`)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["When adding a surface pair, add both tokens to the corresponding variable token array.","Ensure each pair token is also declared in the CSS file (shadcn.css or product.css).","Run the theme contract validator after changes to surface pairs or variable token arrays.","When renaming a token, update it in all three places: the variable array, the surface pairs, and the CSS declaration."],"tags":["theme-contract","build-validation","surface-pairs","missing-variable","configuration"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}