CherryHQ/cherry-studio · error · Error

[theme-contract] ${label} pair ${surface} / ${foreground} is

Error message

[theme-contract] ${label} pair ${surface} / ${foreground} is outside its public contract

What it means

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.

Source

Thrown at packages/ui/scripts/validate-theme-contract.ts:124

function assertUnique(label: string, values: readonly string[]): void {
  if (new Set(values).size !== values.length) {
    throw new Error(`[theme-contract] ${label} contains duplicate names`)
  }
}

function assertSurfacePairs(
  label: string,
  pairs: ReadonlyArray<readonly [surface: string, foreground: string]>,
  variableNames: Set<string>
): void {
  const surfaces = new Set<string>()

  for (const [surface, foreground] of pairs) {
    if (surface === foreground || surfaces.has(surface)) {
      throw new Error(`[theme-contract] ${label} has an invalid or duplicate surface pair for ${surface}`)
    }
    if (!variableNames.has(surface) || !variableNames.has(foreground)) {
      throw new Error(`[theme-contract] ${label} pair ${surface} / ${foreground} is outside its public contract`)
    }
    surfaces.add(surface)
  }
}

function assertExactImports(label: string, source: string, expected: readonly string[]): void {
  const actual = extractImports(source)

  if (actual.length !== expected.length || actual.some((entry, index) => entry !== expected[index])) {
    throw new Error(`[theme-contract] ${label} imports must be exactly: ${expected.join(' -> ')}`)
  }
}

function buildDeclarationMap(entries: SourceEntry[], selector: ':root' | '.dark'): Map<string, Declaration> {
  const declarations = new Map<string, Declaration>()

  for (const [sourceName, source] of entries) {
    for (const declaration of extractModeDeclarations(source, sourceName, selector)) {

View on GitHub (pinned to 726446b54c)

Solutions

  1. 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.
  2. 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).
  3. If the pair references a non-existent token, correct the token name to match an existing one.
  4. Run the validator to confirm: npx tsx packages/ui/scripts/validate-theme-contract.ts.

Example fix

// before — surface pair references undeclared variable
export const SHADCN_VARIABLE_TOKENS = [
  'card', 'card-foreground', 'popover'
  // 'popover-foreground' missing
] as const
export const SHADCN_SURFACE_PAIRS = [
  ['card', 'card-foreground'],
  ['popover', 'popover-foreground']  // ← 'popover-foreground' not in variable list
] as const

// after — add the missing variable
export const SHADCN_VARIABLE_TOKENS = [
  'card', 'card-foreground', 'popover', 'popover-foreground'
] as const
export const SHADCN_SURFACE_PAIRS = [
  ['card', 'card-foreground'],
  ['popover', 'popover-foreground']
] as const
Defensive patterns

Strategy: validation

Validate before calling

// Check that all surface pair tokens exist in the variable token array before committing
import { SHADCN_SURFACE_PAIRS, SHADCN_VARIABLE_TOKENS } from './theme-contract'

const vars = new Set(SHADCN_VARIABLE_TOKENS)
for (const [surface, foreground] of SHADCN_SURFACE_PAIRS) {
  if (!vars.has(surface)) throw new Error(`Surface ${surface} missing from variable tokens`)
  if (!vars.has(foreground)) throw new Error(`Foreground ${foreground} missing from variable tokens`)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/8e3b5669ef1e8ae4. Report an issue: GitHub.