CherryHQ/cherry-studio · error · Error

[theme-contract] ${label} imports must be exactly: ${expecte

Error message

[theme-contract] ${label} imports must be exactly: ${expected.join(' -> ')}

What it means

Thrown by the theme contract validator when the @import statements in a specific CSS file (identified by label) do not exactly match the expected ordered list. The assertExactImports function extracts all @import URLs from the source and compares both count and order against the expected array. This enforces a strict, deterministic import chain for theme CSS entry points (tokens.css, tokens/index.css, contract.css) to guarantee correct CSS cascade ordering and layer dependencies.

Source

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

): 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>()

  for (const [sourceName, source] of entries) {
    for (const declaration of extractModeDeclarations(source, sourceName, selector)) {
      const existing = declarations.get(declaration.name)
      if (existing) {
        throw new Error(
          `[theme-contract] ${declaration.name} is defined twice in ${selector}: ${existing.source} and ${sourceName}`
        )
      }
      declarations.set(declaration.name, declaration)
    }
  }

View on GitHub (pinned to 726446b54c)

Solutions

  1. Compare the actual @import statements in the file identified by the label against the expected list shown in the error message.
  2. Update the CSS file's @import statements to exactly match the expected list in both content and order, OR update the expected array in validate-theme-contract.ts if the change is intentional.
  3. If adding a new token file, update both the CSS @import in the entry file AND the expected array in assertExactImports call.
  4. Run the validator to confirm: npx tsx packages/ui/scripts/validate-theme-contract.ts.

Example fix

/* before — tokens/index.css has wrong import order or extra import */
@import './colors/primitive.css';
@import './colors/providers.css';
@import './colors/status-legacy.css';
@import './spacing.css';
@import './radius.css';
@import './typography.css';
@import './colors/new-token.css';  /* ← not in expected list */

/* after — match expected exactly, or update expected in validator */
/* If the new import is intentional, update validate-theme-contract.ts: */
/*   assertExactImports('tokens/index.css', sources.tokensIndex, [       */
/*     './colors/primitive.css',                                         */
/*     './colors/status-legacy.css',                                     */
/*     './colors/providers.css',                                         */
/*     './colors/new-token.css',                                         */
/*     './spacing.css', './radius.css', './typography.css'               */
/*   ])                                                                  */
/* and ensure the CSS file matches this order.                           */
Defensive patterns

Strategy: validation

Validate before calling

// Verify @import statements match expected list before committing CSS changes
import { readFileSync } from 'node:fs'

function checkExactImports(cssPath: string, expected: readonly string[]): void {
  const source = readFileSync(cssPath, 'utf8')
  const actual = [...source.matchAll(/@import\s+([^;]+);/g)].map(m => {
    const v = m[1].trim()
    const sm = v.match(/^(['"])([^'"]+)\1$/)
    return sm ? sm[2] : v
  })
  if (actual.length !== expected.length || actual.some((e, i) => e !== expected[i])) {
    throw new Error(`${cssPath} imports mismatch. Expected: ${expected.join(' -> ')}, Got: ${actual.join(' -> ')}`)
  }
}

checkExactImports('packages/ui/src/styles/tokens.css', ['./tokens/index.css'])

Prevention

When it happens

Trigger: A CSS file (tokens.css, tokens/index.css, or contract.css) has @import statements that differ from the expected list — either extra imports, missing imports, imports in the wrong order, or wrong import paths. The validator extracts imports via extractImports and checks actual.length === expected.length and each entry matches positionally.

Common situations: A developer adds a new token file and updates tokens/index.css imports but forgets to update the expected list in the validator (or vice versa). A file is renamed and the import path changes but the validator's expected array isn't updated. Import statements are reordered by a formatter or by accident. An import is commented out or removed during debugging.

Related errors


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