CherryHQ/cherry-studio · error · Error
[theme-contract] ${label} references missing foundation vari
Error message
[theme-contract] ${label} references missing foundation variables: ${missing.join(', ')} What it means
Thrown by assertCompatibilityTokensDeclared when a compatibility token list (COMPATIBILITY_SEMANTIC_COLOR_TOKENS or COMPATIBILITY_STATUS_COLOR_TOKENS) contains tokens whose backing `--cs-<token>` foundation variable is not declared in the expected source (providers.css or status-legacy.css). Compatibility tokens are a frozen shrink-only Tailwind surface, but each still requires a real foundation declaration to resolve.
Source
Thrown at packages/ui/scripts/validate-theme-contract.ts:179
): void {
const missing = variableNames.map((name) => `${prefix}${name}`).filter((name) => !declarations.has(name))
if (missing.length > 0) {
throw new Error(`[theme-contract] ${label} is missing root declarations: ${missing.join(', ')}`)
}
}
function assertCompatibilityTokensDeclared(
label: string,
tokenNames: readonly string[],
source: string,
sourceName: string
): void {
const declarations = new Set(extractDeclarations(source, sourceName).map((declaration) => declaration.name))
const missing = tokenNames.map((token) => `--cs-${token}`).filter((name) => !declarations.has(name))
if (missing.length > 0) {
throw new Error(`[theme-contract] ${label} references missing foundation variables: ${missing.join(', ')}`)
}
}
function assertReferencesResolve(mode: string, declarations: Map<string, Declaration>): void {
for (const declaration of declarations.values()) {
for (const reference of extractReferences(declaration.value, declaration.source)) {
if (!declarations.has(reference)) {
throw new Error(
`[theme-contract] ${mode} ${declaration.name} in ${declaration.source} references undefined ${reference}`
)
}
}
}
}
function assertNoCycles(mode: string, declarations: Map<string, Declaration>): void {
const visited = new Set<string>()
const visiting = new Set<string>()View on GitHub (pinned to 726446b54c)
Solutions
- Declare each missing `--cs-<token>` variable in the source named in the label (providers.css for semantic colors, status-legacy.css for status colors).
- If the token should not be exposed, remove it from the compatibility list in scripts/theme-contract.ts instead of adding CSS.
- Re-run `pnpm --filter @cherrystudio/ui theme:check`.
Example fix
// before — theme-contract.ts
export const COMPATIBILITY_SEMANTIC_COLOR_TOKENS = ['destructive-hover'] as const
// providers.css has no --cs-destructive-hover
// after — add to tokens/colors/providers.css
:root { --cs-destructive-hover: oklch(...); } Defensive patterns
Strategy: validation
Validate before calling
// Pre-check that compatibility tokens resolve to --cs-<token> in the expected source.
const declared = new Set([...sources.providerColors.matchAll(/(--cs-[a-z0-9-]+)\s*:/g)].map((m) => m[1]))
const missing = COMPATIBILITY_SEMANTIC_COLOR_TOKENS.map((t) => `--cs-${t}`).filter((n) => !declared.has(n))
if (missing.length) throw new Error(`compatibility tokens missing foundation: ${missing.join(', ')}`) Try / catch
try {
validateThemeContractSources(sources)
} catch (error) {
if (error instanceof Error && /references missing foundation variables/.test(error.message)) {
console.error(error.message)
process.exitCode = 1
return
}
throw error
} Prevention
- When extending a COMPATIBILITY_*_COLOR_TOKENS list, add the backing --cs-<token> to the matching token CSS file in the same change.
- Remember compatibility lists are shrink-only: prefer removing a legacy token over adding one.
- Run theme:check after any edit to scripts/theme-contract.ts.
When it happens
Trigger: Adding 'destructive-hover' to COMPATIBILITY_SEMANTIC_COLOR_TOKENS in theme-contract.ts without declaring `--cs-destructive-hover` in tokens/colors/providers.css; declaring the backing var in the wrong file (e.g. primitive.css instead of providers.css).
Common situations: Reviving a legacy Tailwind utility class by adding it to the compatibility list but forgetting the CSS half; moving provider colors between token files during a refactor.
Related errors
- [theme-contract] ${declaration.name} is defined twice in ${s
- [theme-contract] ${label} is missing root declarations: ${mi
- [theme-contract] ${mode} ${declaration.name} in ${declaratio
- [theme-contract] ${mode} variable cycle: ${[...stack.slice(c
- [theme-contract] foundation cannot declare runtime input ${d
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/2183ba7959f9383b.
Report an issue: GitHub.