CherryHQ/cherry-studio · error · Error

[theme-contract] ${label} is missing root declarations: ${mi

Error message

[theme-contract] ${label} is missing root declarations: ${missing.join(', ')}

What it means

Thrown by assertRequiredDeclarations when a registered contract token list (runtime inputs, Shadcn variables, or product variables) has entries with no matching `--<prefix><token>` declaration in the :root block of the owning source. The contract is closed: every registered token MUST be declared in :root so consumers always resolve. The message lists every missing prefixed name.

Source

Thrown at packages/ui/scripts/validate-theme-contract.ts:165

        )
      }
      declarations.set(declaration.name, declaration)
    }
  }

  return declarations
}

function assertRequiredDeclarations(
  label: string,
  declarations: Map<string, Declaration>,
  variableNames: readonly string[],
  prefix: string
): 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 {

View on GitHub (pinned to 726446b54c)

Solutions

  1. Restore the missing declaration(s) into the :root block of the source named in the label (label maps to file: 'Shadcn contract in shadcn.css' -> shadcn.css, 'product contract in product.css' -> product.css, 'runtime theme inputs' -> the aggregated :root graph).
  2. If a token was deliberately removed from the CSS, also remove it from the corresponding token registry in scripts/theme-contract.ts (SHADCN_VARIABLE_TOKENS / CHERRY_PRODUCT_VARIABLE_TOKENS / RUNTIME_THEME_INPUT_TOKENS).
  3. Re-run `pnpm --filter @cherrystudio/ui theme:check`.

Example fix

// before — :root block of shadcn.css with --background removed
// after
:root {
  --background: var(--cs-background);
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check that every registered token has a :root declaration in its owning source.
const required = new Set([...SHADCN_VARIABLE_TOKENS, ...CHERRY_PRODUCT_VARIABLE_TOKENS].map((t) => `--${t}`))
const declared = new Set([...sources.shadcn.matchAll(/^[\s{;]*(--[a-z0-9-]+)\s*:/gm), ...sources.product.matchAll(/^[\s{;]*(--[a-z0-9-]+)\s*:/gm)].map((m) => m[1]))
const missing = [...required].filter((n) => !declared.has(n))
if (missing.length) throw new Error(`missing :root declarations: ${missing.join(', ')}`)

Try / catch

try {
  validateThemeContractSources(sources)
} catch (error) {
  if (error instanceof Error && /is missing root declarations/.test(error.message)) {
    console.error(error.message) // lists the exact missing variables
    process.exitCode = 1
    return
  }
  throw error
}

Prevention

When it happens

Trigger: Deleting `--background: var(--cs-background);` from shadcn.css :root; deleting `--chat-user: ...;` from product.css :root; declaring a required variable only inside `.dark` and not in `:root`.

Common situations: Cleaning up 'unused' variables that are actually contract-mandated; moving a declaration into a nested selector; rebasing across a commit that added a new token to SHADCN_VARIABLE_TOKENS / CHERRY_PRODUCT_VARIABLE_TOKENS without authoring the CSS.

Related errors


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