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
- 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.
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 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.
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
- [theme-contract] ${label} has an invalid or duplicate surfac
- [theme-contract] ${label} contains duplicate names
- [theme-contract] renderer theme must use the shared generate
- [theme-contract] renderer stylesheet ${fileName} cannot use
- [theme-contract] renderer TypeScript source ${fileName} cann
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/8e3b5669ef1e8ae4.
Report an issue: GitHub.