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
- Compare the actual @import statements in the file identified by the label against the expected list shown in the error message.
- 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.
- If adding a new token file, update both the CSS @import in the entry file AND the expected array in assertExactImports call.
- 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 modifying CSS @import statements in theme entry files (tokens.css, tokens/index.css, contract.css), update the corresponding assertExactImports expected array in validate-theme-contract.ts.
- Keep the import order deterministic — avoid letting formatters reorder @import statements.
- Run the theme contract validator after any change to theme CSS imports.
- Document the required import chain in a README within packages/ui/src/styles/ for new developers.
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
- [theme-contract] unsupported @import syntax: ${importValue}
- [theme-contract] renderer theme must use the shared generate
- [theme-contract] renderer stylesheet ${fileName} cannot use
- [theme-contract] ${sourceName} declares invalid custom prope
- [theme-contract] ${sourceName} references invalid custom pro
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/cb49e594d9ebd45f.
Report an issue: GitHub.