CherryHQ/cherry-studio · error · Error
[theme-contract] ${label} has an invalid or duplicate surfac
Error message
[theme-contract] ${label} has an invalid or duplicate surface pair for ${surface} What it means
Thrown by the theme contract validator when a surface-pair list (SHADCN_SURFACE_PAIRS or CHERRY_PRODUCT_SURFACE_PAIRS) has an invalid entry: either the surface and its foreground are the same token (surface === foreground), or the same surface token appears in more than one pair. Surface pairs define matched background/foreground combos (e.g., ['card', 'card-foreground']) and each surface must be unique and distinct from its foreground.
Source
Thrown at packages/ui/scripts/validate-theme-contract.ts:121
})
}
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>()View on GitHub (pinned to 726446b54c)
Solutions
- Inspect the surface pair array identified by the label (e.g., 'Shadcn contract', 'product contract') in packages/ui/scripts/theme-contract.ts.
- If surface === foreground, correct the foreground to the intended distinct token.
- If a surface is duplicated across pairs, remove or rename the duplicate so each surface appears exactly once.
- Run the validator to confirm: npx tsx packages/ui/scripts/validate-theme-contract.ts.
Example fix
// before export const SHADCN_SURFACE_PAIRS = [ ['card', 'card-foreground'], ['card', 'card-muted-foreground'], // ← duplicate surface 'card' ['popover', 'popover'] // ← surface === foreground ] as const // after — distinct, unique surfaces export const SHADCN_SURFACE_PAIRS = [ ['card', 'card-foreground'], ['popover', 'popover-foreground'] ] as const
Defensive patterns
Strategy: validation
Validate before calling
// Check surface pairs for duplicates and self-references before committing
import { SHADCN_SURFACE_PAIRS, CHERRY_PRODUCT_SURFACE_PAIRS } from './theme-contract'
function checkSurfacePairs(label: string, pairs: ReadonlyArray<readonly [string, string]>): void {
const surfaces = new Set<string>()
for (const [surface, foreground] of pairs) {
if (surface === foreground) throw new Error(`${label}: ${surface} pairs with itself`)
if (surfaces.has(surface)) throw new Error(`${label}: duplicate surface ${surface}`)
surfaces.add(surface)
}
}
checkSurfacePairs('Shadcn', SHADCN_SURFACE_PAIRS)
checkSurfacePairs('product', CHERRY_PRODUCT_SURFACE_PAIRS) Prevention
- When adding a surface pair, verify the surface name isn't already used in another pair.
- Ensure surface and foreground are always distinct tokens.
- Run the theme contract validator after any change to surface pair arrays.
- Use a consistent naming pattern (e.g., surface + '-foreground') to avoid mismatches.
When it happens
Trigger: A surface pair array contains a self-referential pair like ['card', 'card'] (surface equals foreground), or the same surface name appears in two different pairs (e.g., ['card', 'card-foreground'] and ['card', 'card-muted'] both list 'card'). The assertSurfacePairs function iterates all pairs and tracks seen surfaces in a Set.
Common situations: A developer adds a new surface pair but reuses an existing surface name. A copy-paste error duplicates a pair entry. A typo where the foreground was set to the same value as the surface. A refactoring that merged or split pairs incorrectly.
Related errors
- [theme-contract] ${label} contains duplicate names
- [theme-contract] ${label} pair ${surface} / ${foreground} is
- [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/4696263c8612a783.
Report an issue: GitHub.