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

  1. 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.
  2. If it belongs to another layer (foundation --cs-*, runtime --cs-theme-*, or Shadcn), move it to the correct file.
  3. 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

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


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