CherryHQ/cherry-studio · error · Error
[theme-contract] product.css declares unregistered product v
Error message
[theme-contract] product.css declares unregistered product variable ${name} What it means
Thrown by validateThemeContractSources when product.css declares a --<name> variable that is not in CHERRY_PRODUCT_VARIABLE_TOKENS. The product contract is a closed set; only registered product variables may be declared in product.css. This catches invented product roles, typos, and declarations that belong in another layer.
Source
Thrown at packages/ui/scripts/validate-theme-contract.ts:413
['theme-input.css', sources.themeInput],
['shadcn.css', sources.shadcn],
['product.css', sources.product]
]
const rootDeclarations = buildDeclarationMap(orderedSources, ':root')
const darkOverrides = buildDeclarationMap(orderedSources, '.dark')
const darkDeclarations = new Map(rootDeclarations)
for (const [name, declaration] of darkOverrides) darkDeclarations.set(name, declaration)
assertRequiredDeclarations('runtime theme inputs', rootDeclarations, RUNTIME_THEME_INPUT_TOKENS, '--cs-theme-')
assertRequiredDeclarations('Shadcn contract', rootDeclarations, SHADCN_VARIABLE_TOKENS, '--')
assertRequiredDeclarations('product contract', rootDeclarations, CHERRY_PRODUCT_VARIABLE_TOKENS, '--')
const productDeclarationNames = new Set(
extractDeclarations(sources.product, 'product.css').map((declaration) => declaration.name)
)
for (const name of productDeclarationNames) {
if (!productVariableNames.has(name)) {
throw new Error(`[theme-contract] product.css declares unregistered product variable ${name}`)
}
}
assertReferencesResolve('light', rootDeclarations)
assertReferencesResolve('dark', darkDeclarations)
assertNoCycles('light', rootDeclarations)
assertNoCycles('dark', darkDeclarations)
}
export async function loadThemeContractSources(stylesDir = DEFAULT_STYLES_DIR): Promise<ThemeContractSources> {
const tokensDir = path.join(stylesDir, 'tokens')
const [
variableCatalog,
contractEntry,
tokensEntry,
tokensIndex,
themeInput,
primitiveColors,View on GitHub (pinned to 726446b54c)
Solutions
- If it is a genuine new product variable, register it in CHERRY_PRODUCT_VARIABLE_TOKENS (and CHERRY_PRODUCT_COLOR_TOKENS if it needs a Tailwind utility, plus document it in variable-catalog.md), then keep the declaration in product.css.
- If it belongs to another layer (foundation --cs-*, runtime --cs-theme-*, or Shadcn), move it to the correct file.
- Re-run `pnpm --filter @cherrystudio/ui theme:check`.
Example fix
// before (product.css)
:root { --chat-bubble: red; }
// 'chat-bubble' is not registered
// after — register in scripts/theme-contract.ts
export const CHERRY_PRODUCT_VARIABLE_TOKENS = ['chat-bubble', ...] as const
// and add `--chat-bubble` to variable-catalog.md Defensive patterns
Strategy: validation
Validate before calling
// Every variable declared in product.css must be a registered product variable.
const allowed = new Set(CHERRY_PRODUCT_VARIABLE_TOKENS.map((t) => `--${t}`))
const offenders = [...sources.product.matchAll(/(?:^|[;{])\s*(--[a-z0-9-]+)\s*:/g)].map((m) => m[1]).filter((n) => !allowed.has(n))
if (offenders.length) throw new Error(`product.css declares unregistered vars: ${offenders.join(', ')}`) Try / catch
try {
validateThemeContractSources(sources)
} catch (error) {
if (error instanceof Error && /product\.css declares unregistered product variable/.test(error.message)) {
console.error(error.message)
process.exitCode = 1
return
}
throw error
} Prevention
- Only CHERRY_PRODUCT_VARIABLE_TOKENS may be declared in product.css.
- Adding a product variable means updating the registry, the CSS, the Tailwind color list (if needed), and variable-catalog.md together.
- Run theme:check after editing product.css or the product registry.
When it happens
Trigger: Adding `:root { --chat-bubble: red; }` to product.css without registering 'chat-bubble' in CHERRY_PRODUCT_VARIABLE_TOKENS; pasting a foundation --cs-* declaration into product.css.
Common situations: Adding a new product role and forgetting the registry/catalog half; leftover experimental declarations; rebasing across a product-token rename.
Related errors
- [theme-contract] theme-input.css declares unregistered runti
- [theme-contract] product variable ${token} overlaps the offi
- [theme-contract] Tailwind product color ${token} is missing
- [theme-contract] compatibility color ${token} overlaps the c
- [theme-contract] shadcn.css declares unregistered Shadcn var
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/e147468a9994aeed.
Report an issue: GitHub.