import { MODES } from '../constants'; import { generateColorScale } from '../palette'; import type { Mode, Platform, Preset } from '../types'; import { hasOwn } from '../utils/common'; import { overrideFontCatalog, overrideRuntimeData } from './artifacts'; import type { ConcreteTokenValue, ExpandThemeOverride, ExpandThemeOverrideRequest, ExpandThemeOverrideResult, ExpandThemeOverrideWithData, ExpandedThemeOverride, InputDefinition, InputId, ModeTokenState, OverrideDiagnostic, OverrideRuntimeData, PackageTokenExpression, ParsedThemeOverride, SelectedValue, TokenOverride, TokenPath, TokenProvenance, } from './contracts'; import { createOverrideDiagnostic, hasErrorDiagnostics } from './diagnostics'; import { applyOverrideInputTransform } from './inputTransforms'; import { parseThemeOverride } from './parser'; import { expandSelectionExpressions } from './recipeEngine'; import { evaluateIntentRules, evaluateResolvedRules } from './ruleEvaluation'; interface MutableModeState { light: Record; dark: Record; } interface ResolvedPresetState { readonly values: MutableModeState; readonly provenance: MutableModeState; readonly diagnostics: readonly OverrideDiagnostic[]; } function emptyModeState(): MutableModeState { return { light: {}, dark: {} }; } function clonePrimitiveState( preset: Preset, data: OverrideRuntimeData, ): MutableModeState { return { light: { ...data.primitives.byPreset[preset] }, dark: { ...data.primitives.byPreset[preset] }, }; } function expressionMapForMode( request: ExpandThemeOverrideRequest, preset: Preset, mode: Mode, ): ReadonlyMap { const result = new Map(); const expressions = request.data.expressions.byPreset[preset]; const apply = (predicate: (entry: (typeof expressions)[number]) => boolean) => { for (const entry of expressions) { if (predicate(entry)) result.set(entry.tokenPath, entry.expression); } }; apply((entry) => entry.mode === 'invariant' && entry.platform === undefined); apply((entry) => entry.mode === 'invariant' && entry.platform === request.platform); apply((entry) => entry.mode === mode && entry.platform === undefined); apply((entry) => entry.mode === mode && entry.platform === request.platform); return result; } function resolvePresetState( request: ExpandThemeOverrideRequest, preset: Preset, primitives: MutableModeState, primitiveProvenance?: MutableModeState, ): ResolvedPresetState { const values = emptyModeState(); const provenance = emptyModeState(); const diagnostics: OverrideDiagnostic[] = []; for (const mode of MODES) { const expressions = expressionMapForMode(request, preset, mode); const resolving = new Set(); const resolvePath = (tokenPath: TokenPath): ConcreteTokenValue | undefined => { if (tokenPath in values[mode]) return values[mode][tokenPath]; const explicitPrimitiveProvenance = primitiveProvenance?.[mode][tokenPath]; if (explicitPrimitiveProvenance && tokenPath in primitives[mode]) { values[mode][tokenPath] = primitives[mode][tokenPath]; provenance[mode][tokenPath] = explicitPrimitiveProvenance; return values[mode][tokenPath]; } if (resolving.has(tokenPath)) { diagnostics.push( createOverrideDiagnostic('override.unresolved_reference', { mode, tokenPath, artifactRevision: request.data.revision.id, message: `Preset expression cycle detected at ${tokenPath}.`, }), ); return undefined; } const expression = expressions.get(tokenPath); if (!expression) { if (!hasOwn(primitives[mode], tokenPath)) return undefined; const primitive = primitives[mode][tokenPath]; values[mode][tokenPath] = primitive; provenance[mode][tokenPath] = { layer: 'preset' }; return primitive; } resolving.add(tokenPath); const value = expression.kind === 'literal' ? expression.value : resolvePath(expression.ref); resolving.delete(tokenPath); if (value === undefined) { diagnostics.push( createOverrideDiagnostic('override.unresolved_reference', { mode, tokenPath, relatedTokenPaths: expression.kind === 'reference' ? [expression.ref] : undefined, artifactRevision: request.data.revision.id, message: expression.kind === 'reference' ? `Preset expression ${tokenPath} cannot resolve "${expression.ref}".` : `Preset expression ${tokenPath} has no concrete value.`, }), ); return undefined; } values[mode][tokenPath] = value; provenance[mode][tokenPath] = { layer: 'preset', ...(expression.kind === 'reference' ? { sourceRef: expression.ref } : {}), }; return value; }; const paths = new Set([...expressions.keys(), ...Object.keys(primitives[mode])]); for (const tokenPath of paths) { resolvePath(tokenPath); } } return { values, provenance, diagnostics }; } function changedPresetKeys( request: ExpandThemeOverrideRequest, selected: ResolvedPresetState, ): Readonly>> { if (request.parsed.metadata.basePreset === 'poise') { return { light: new Set(), dark: new Set() }; } const baselinePrimitives = clonePrimitiveState('poise', request.data); const baseline = resolvePresetState(request, 'poise', baselinePrimitives); const result: Record> = { light: new Set(), dark: new Set(), }; for (const mode of MODES) { const paths = new Set([ ...Object.keys(selected.values[mode]), ...Object.keys(baseline.values[mode]), ]); for (const path of paths) { if (selected.values[mode][path] !== baseline.values[mode][path]) { result[mode].add(path); } } } return result; } function inputProvenance(inputId: InputId, sourceRef?: string): TokenProvenance { return { layer: 'input', inputId, intentPath: `inputs.${inputId}`, ...(sourceRef ? { sourceRef } : {}), }; } function applyPalette( seed: string, category: 'brand' | 'accent' | 'base', inputId: InputId, data: OverrideRuntimeData, primitives: MutableModeState, primitiveProvenance: MutableModeState, writes: MutableModeState, writeProvenance: MutableModeState, ): void { const generated = generateColorScale(seed, category); for (const mode of MODES) { for (const [tokenPath, value] of Object.entries(generated)) { if (!hasOwn(data.primitives.types, tokenPath)) continue; const provenance = inputProvenance(inputId); primitives[mode][tokenPath] = value; primitiveProvenance[mode][tokenPath] = provenance; writes[mode][tokenPath] = value; writeProvenance[mode][tokenPath] = provenance; } } } function isSelectedValue(value: TokenOverride): value is SelectedValue { return 'ref' in value || 'value' in value; } function resolveSelectedValue( value: SelectedValue, primitives: Readonly>, ): ConcreteTokenValue | undefined { return 'ref' in value ? primitives[value.ref] : value.value; } function applyDirectPrimitiveOverrides( parsed: ParsedThemeOverride, data: OverrideRuntimeData, primitives: MutableModeState, primitiveProvenance: MutableModeState, writes: MutableModeState, writeProvenance: MutableModeState, diagnostics: OverrideDiagnostic[], ): void { const preset = parsed.metadata.basePreset; const primitivePaths = new Set(Object.keys(data.primitives.byPreset[preset])); const snapshot = { light: { ...primitives.light }, dark: { ...primitives.dark }, }; for (const [tokenPath, override] of Object.entries(parsed.overrides.known)) { if (!primitivePaths.has(tokenPath) || !isSelectedValue(override)) continue; for (const mode of MODES) { const value = resolveSelectedValue(override, snapshot[mode]); if (value === undefined) { diagnostics.push( createOverrideDiagnostic('override.unresolved_reference', { mode, intentPath: `overrides.${tokenPath}`, tokenPath, artifactRevision: data.revision.id, }), ); continue; } const provenance: TokenProvenance = { layer: 'direct', intentPath: `overrides.${tokenPath}`, ...('ref' in override ? { sourceRef: override.ref } : {}), }; primitives[mode][tokenPath] = value; primitiveProvenance[mode][tokenPath] = provenance; writes[mode][tokenPath] = value; writeProvenance[mode][tokenPath] = provenance; } diagnostics.push( createOverrideDiagnostic('override.primitive_step_frozen', { intentPath: `overrides.${tokenPath}`, tokenPath, artifactRevision: data.revision.id, }), ); } } function applyFontInput( input: InputDefinition, family: string, parsed: ParsedThemeOverride, platform: Platform, resolved: MutableModeState, resolvedProvenance: MutableModeState, writes: MutableModeState, writeProvenance: MutableModeState, diagnostics: OverrideDiagnostic[], ): void { const preset = parsed.metadata.basePreset; if (!overrideFontCatalog.presetFamilies[preset].includes(family)) { diagnostics.push( createOverrideDiagnostic('override.invalid_value', { phase: 'intent', intentPath: `inputs.${input.id}`, message: `"${family}" is not an allowed Display family for ${preset}.`, }), ); return; } if (!hasOwn(overrideFontCatalog.families, family)) { diagnostics.push( createOverrideDiagnostic('override.invalid_value', { phase: 'expand', intentPath: `inputs.${input.id}`, message: `Display family "${family}" has no ${platform} face mapping.`, }), ); return; } const faces = overrideFontCatalog.families[family].byPlatform[platform]; for (const mode of MODES) { for (const [tokenPath, value] of Object.entries(faces)) { const provenance = inputProvenance(input.id); resolved[mode][tokenPath] = value; resolvedProvenance[mode][tokenPath] = provenance; writes[mode][tokenPath] = value; writeProvenance[mode][tokenPath] = provenance; } } } function orderedInputs( data: OverrideRuntimeData, executionPhase: InputDefinition['executionPhase'], ): readonly InputDefinition[] { return data.catalogs .listInputs() .filter((input) => input.executionPhase === executionPhase) .sort((left, right) => left.priority - right.priority); } function applyPrimitiveInputs( parsed: ParsedThemeOverride, data: OverrideRuntimeData, primitives: MutableModeState, primitiveProvenance: MutableModeState, writes: MutableModeState, writeProvenance: MutableModeState, ): void { for (const input of orderedInputs(data, 'primitive')) { if (input.strategy.kind !== 'palette') continue; let seed = parsed.inputs.known[input.id]; let provenanceInputId = input.id; const fallback = input.strategy.fallback; if (seed === undefined && fallback) { const fallbackSeed = parsed.inputs.known[fallback.inputId]; if (fallbackSeed !== undefined) { seed = applyOverrideInputTransform(fallback.transform, fallbackSeed); provenanceInputId = fallback.inputId; } } if (seed === undefined) continue; applyPalette( seed, input.strategy.category, provenanceInputId, data, primitives, primitiveProvenance, writes, writeProvenance, ); } } function applyResolvedInputs( parsed: ParsedThemeOverride, platform: Platform, data: OverrideRuntimeData, resolved: MutableModeState, resolvedProvenance: MutableModeState, writes: MutableModeState, writeProvenance: MutableModeState, diagnostics: OverrideDiagnostic[], ): void { for (const input of orderedInputs(data, 'resolved')) { const value = parsed.inputs.known[input.id]; if (value === undefined) continue; switch (input.strategy.kind) { case 'display-font': applyFontInput( input, value, parsed, platform, resolved, resolvedProvenance, writes, writeProvenance, diagnostics, ); break; case 'palette': break; } } } function applyExpressionWrites( expressions: ModeTokenState, provenanceFor: ( mode: Mode, tokenPath: TokenPath, expression: PackageTokenExpression, ) => TokenProvenance, primitives: MutableModeState, resolved: MutableModeState, resolvedProvenance: MutableModeState, writes: MutableModeState, writeProvenance: MutableModeState, diagnostics: OverrideDiagnostic[], revision: string, ): void { for (const mode of MODES) { for (const [tokenPath, expression] of Object.entries(expressions[mode])) { let value: ConcreteTokenValue; if (expression.kind === 'literal') { value = expression.value; } else if (!hasOwn(primitives[mode], expression.ref)) { diagnostics.push( createOverrideDiagnostic('override.unresolved_reference', { mode, tokenPath, relatedTokenPaths: [expression.ref], artifactRevision: revision, }), ); continue; } else { value = primitives[mode][expression.ref]; } const provenance = provenanceFor(mode, tokenPath, expression); resolved[mode][tokenPath] = value; resolvedProvenance[mode][tokenPath] = provenance; writes[mode][tokenPath] = value; writeProvenance[mode][tokenPath] = provenance; } } } function directSemanticExpressions( tokenPath: TokenPath, override: TokenOverride, ): ModeTokenState { const toExpression = (value: SelectedValue): PackageTokenExpression => 'ref' in value ? { kind: 'reference', ref: value.ref } : { kind: 'literal', value: value.value }; if (isSelectedValue(override)) { const expression = toExpression(override); return { light: { [tokenPath]: expression }, dark: { [tokenPath]: expression } }; } return { light: override.light ? { [tokenPath]: toExpression(override.light) } : {}, dark: override.dark ? { [tokenPath]: toExpression(override.dark) } : {}, }; } function hasConcreteOutput(state: MutableModeState): boolean { return MODES.every((mode) => Object.values(state[mode]).every( (value) => typeof value === 'number' || (!/^\{[^{}]+\}$/.test(value) && !/^var\(/i.test(value)), ), ); } function freezeResult( basePreset: Preset, platform: Platform, resolved: MutableModeState, writes: MutableModeState, resolvedProvenance: MutableModeState, writeProvenance: MutableModeState, diagnostics: readonly OverrideDiagnostic[], data: OverrideRuntimeData, ): ExpandedThemeOverride { return { basePreset, platform, resolved, writes, writtenKeys: { light: Object.keys(writes.light).sort(), dark: Object.keys(writes.dark).sort(), }, provenance: { resolved: resolvedProvenance, writes: writeProvenance, }, diagnostics, revision: data.revision, }; } function catalogIntegrityDiagnostics(data: OverrideRuntimeData): readonly OverrideDiagnostic[] { const owners = new Map(); const diagnostics: OverrideDiagnostic[] = []; for (const selection of data.catalogs.listSelections()) { const recipe = data.catalogs.getRecipe(selection.recipeId); if (!recipe) continue; for (const tokenPath of recipe.managedTargets) { const prior = owners.get(tokenPath); if (prior) { diagnostics.push( createOverrideDiagnostic('override.invalid_structure', { phase: 'expand', tokenPath, message: `Recipes "${prior}" and "${recipe.id}" both manage ${tokenPath} without an overlap policy.`, artifactRevision: data.revision.id, }), ); } else { owners.set(tokenPath, recipe.id); } } } return diagnostics; } export const expandThemeOverrideWithData: ExpandThemeOverrideWithData = ( request, ): ExpandThemeOverrideResult => { const { parsed, platform, data } = request; const diagnostics: OverrideDiagnostic[] = [ ...catalogIntegrityDiagnostics(data), ...evaluateIntentRules(data.rules, parsed, data.catalogs.getSourceSet), ]; if (hasErrorDiagnostics(diagnostics)) { return { valid: false, diagnostics, revision: data.revision }; } const preset = parsed.metadata.basePreset; const primitives = clonePrimitiveState(preset, data); const primitiveProvenance = emptyModeState(); const writes = emptyModeState(); const writeProvenance = emptyModeState(); try { applyPrimitiveInputs(parsed, data, primitives, primitiveProvenance, writes, writeProvenance); } catch (cause) { diagnostics.push( createOverrideDiagnostic('override.invalid_value', { phase: 'expand', message: cause instanceof Error ? cause.message : 'Color input expansion failed.', artifactRevision: data.revision.id, }), ); } applyDirectPrimitiveOverrides( parsed, data, primitives, primitiveProvenance, writes, writeProvenance, diagnostics, ); if (hasErrorDiagnostics(diagnostics)) { return { valid: false, diagnostics, revision: data.revision }; } const presetState = resolvePresetState(request, preset, primitives, primitiveProvenance); diagnostics.push(...presetState.diagnostics); if (hasErrorDiagnostics(diagnostics)) { return { valid: false, diagnostics, revision: data.revision }; } const resolved = presetState.values; const resolvedProvenance = presetState.provenance; const cleanPreset = resolvePresetState(request, preset, clonePrimitiveState(preset, data)); const presetChangedKeys = changedPresetKeys(request, cleanPreset); for (const mode of MODES) { for (const tokenPath of presetChangedKeys[mode]) { if (!hasOwn(resolved[mode], tokenPath) || hasOwn(writes[mode], tokenPath)) continue; const value = resolved[mode][tokenPath]; writes[mode][tokenPath] = value; writeProvenance[mode][tokenPath] = resolvedProvenance[mode][tokenPath] ?? { layer: 'preset', }; } } applyResolvedInputs( parsed, platform, data, resolved, resolvedProvenance, writes, writeProvenance, diagnostics, ); if (hasErrorDiagnostics(diagnostics)) { return { valid: false, diagnostics, revision: data.revision }; } const selections = [...data.catalogs.listSelections()].sort( (left, right) => left.priority - right.priority, ); for (const definition of selections) { const selectionId = definition.id; const selection = parsed.selections.known[selectionId]; if (selection === undefined) continue; const recipe = data.catalogs.getRecipe(definition.recipeId); if (!recipe) { diagnostics.push( createOverrideDiagnostic('override.invalid_structure', { phase: 'expand', intentPath: `selections.${selectionId}`, message: `Selection "${selectionId}" has no executable package recipe.`, artifactRevision: data.revision.id, }), ); continue; } const expansion = expandSelectionExpressions(selectionId, selection, preset, data.catalogs); diagnostics.push(...expansion.diagnostics); applyExpressionWrites( expansion.expressions, (_mode, _tokenPath, expression) => ({ layer: 'selection', selectionId, recipeId: recipe.id, intentPath: `selections.${selectionId}`, ...(expression.kind === 'reference' ? { sourceRef: expression.ref } : {}), }), primitives, resolved, resolvedProvenance, writes, writeProvenance, diagnostics, data.revision.id, ); } const primitivePaths = new Set(Object.keys(data.primitives.byPreset[preset])); for (const [tokenPath, override] of Object.entries(parsed.overrides.known)) { if (primitivePaths.has(tokenPath)) continue; applyExpressionWrites( directSemanticExpressions(tokenPath, override), (_mode, _path, expression) => ({ layer: 'direct', intentPath: `overrides.${tokenPath}`, ...(expression.kind === 'reference' ? { sourceRef: expression.ref } : {}), }), primitives, resolved, resolvedProvenance, writes, writeProvenance, diagnostics, data.revision.id, ); } if (!hasConcreteOutput(resolved) || !hasConcreteOutput(writes)) { diagnostics.push( createOverrideDiagnostic('override.unresolved_reference', { artifactRevision: data.revision.id, message: 'Expansion produced a non-concrete alias or CSS variable value.', }), ); } if (hasErrorDiagnostics(diagnostics)) { return { valid: false, diagnostics, revision: data.revision }; } let expanded = freezeResult( preset, platform, resolved, writes, resolvedProvenance, writeProvenance, diagnostics, data, ); const ruleDiagnostics = evaluateResolvedRules(data.rules, expanded); diagnostics.push(...ruleDiagnostics); if (hasErrorDiagnostics(diagnostics)) { return { valid: false, diagnostics, revision: data.revision }; } expanded = freezeResult( preset, platform, resolved, writes, resolvedProvenance, writeProvenance, diagnostics, data, ); return { valid: true, expanded }; }; export function expandParsedThemeOverride( parsed: ParsedThemeOverride, platform: Platform, ): ExpandThemeOverrideResult { return expandThemeOverrideWithData({ parsed, platform, data: overrideRuntimeData }); } function isParsedThemeOverride(value: unknown): value is ParsedThemeOverride { return ( typeof value === 'object' && value !== null && 'kind' in value && value.kind === 'parsed-theme-override' ); } export const expandThemeOverride: ExpandThemeOverride = (override, platform) => { let parsedOverride: ParsedThemeOverride; let parseDiagnostics: readonly OverrideDiagnostic[] = []; if (isParsedThemeOverride(override)) { parsedOverride = override; } else { const parsed = parseThemeOverride(override); if (!parsed.valid) { return { valid: false, diagnostics: parsed.diagnostics, revision: overrideRuntimeData.revision, }; } parsedOverride = parsed.parsed; parseDiagnostics = parsed.diagnostics; } const result = expandParsedThemeOverride(parsedOverride, platform); if (!result.valid || parseDiagnostics.length === 0) return result; return { valid: true, expanded: { ...result.expanded, diagnostics: [...parseDiagnostics, ...result.expanded.diagnostics], }, }; };