repairTheme function
Repairs every failing guardrail of spec (labels, display texts, then
operator/= distinctness), and returns the repaired spec, or null if it
still fails. A passing spec is returned unchanged.
Implementation
ThemeSpec? repairTheme(ThemeSpec spec, {GuardrailLimits? limits}) {
final lim = limits ?? GuardrailLimits.forSpec(spec);
var violations = validateTheme(spec, limits: lim);
if (violations.isEmpty) return spec;
final rules = {for (final v in violations) v.rule};
var p = spec.palette;
final bgs = backgroundSamples(spec.background);
final policy = lim.labelPolicy;
// 1. Operator / `=` faces away from digits (re-solving their labels).
if (spec.nonColourOperatorCue == OperatorCue.none) {
if (rules.contains(GuardrailRule.operatorVsDigit)) {
final op = separateFromDigit(p.operator, p.digit.face, lim);
if (op == null) return null;
p = p.copyWith(operator: op);
}
if (rules.contains(GuardrailRule.equalsVsDigit) ||
rules.contains(GuardrailRule.equalsVsOperator)) {
final eq = separateFromDigit(
p.equals,
p.digit.face,
lim,
avoid: p.operator.face,
avoidDeltaE: lim.equalsVsOperatorDeltaE,
);
if (eq == null) return null;
p = p.copyWith(equals: eq);
}
} else if (rules.contains(GuardrailRule.equalsVsOperator)) {
final eq = separateFromDigit(
p.equals,
p.operator.face,
lim.copyWithDistinct(faceContrast: 1, cvdDeltaE: 0.05),
);
if (eq == null) return null;
p = p.copyWith(equals: eq);
}
// Glass: translucent tints and their labels (liquid_glass.dart).
if (spec.family == ThemeFamily.liquidGlass) {
final glass = repairGlassKeys(p, lim, bgs);
if (glass == null) return null;
p = glass;
}
// 2. Labels.
final digit = repairKeyLabel(p.digit, policy, bgs);
final op = repairKeyLabel(p.operator, policy, bgs);
final eq = repairKeyLabel(p.equals, policy, bgs);
final fn = repairKeyLabel(p.function, policy, bgs);
if (digit == null || op == null || eq == null || fn == null) return null;
p = p.copyWith(digit: digit, operator: op, equals: eq, function: fn);
// 3. Display texts.
final backdrops = displayBackdrops(spec.copyWith(palette: p));
final display = solveOnAll(p.displayText, backdrops, lim.displayRatio);
final error = solveOnAll(p.error, backdrops, lim.displayRatio);
final preview = solveOnAll(p.previewText, backdrops, lim.previewRatio);
final caret = solveOnAll(p.caret, backdrops, lim.caretRatio);
final ring = solveOnAll(p.focusRing, bgs, lim.focusRingRatio);
if (display == null ||
error == null ||
preview == null ||
caret == null ||
ring == null) {
return null;
}
p = p.copyWith(
displayText: display,
error: error,
previewText: preview,
caret: caret,
focusRing: ring,
);
if (rules.contains(GuardrailRule.lcdGhost)) {
p = p.copyWith(lcdGhost: lcdGhostColor(backdrops.first, p.displayText));
}
if (spec.keyStyle.kind == KeyStyleKind.soft) {
final soft = _repairSoft(p, spec.palette, lim, bgs, backdrops);
if (soft == null) return null;
p = soft;
}
if (spec.family == ThemeFamily.neoBrutalism) {
final brutal = _repairBrutal(p, bgs);
if (brutal == null) return null;
p = brutal;
}
if (spec.family == ThemeFamily.liquidGlass) {
final glass = repairGlassExtras(p, spec, lim, bgs);
if (glass == null) return null;
p = glass;
}
final extras = switch (spec.family) {
ThemeFamily.claymorphism => repairClaymorphismExtras(p, spec, lim, bgs),
ThemeFamily.y2kChrome => repairY2kAeroExtras(p, spec, lim, bgs),
ThemeFamily.paperInk => repairPaperInkExtras(p, spec, lim, bgs),
ThemeFamily.cyberpunk => repairCyberpunkExtras(p, spec, lim, bgs),
ThemeFamily.vaporwave => repairVaporwaveExtras(p, spec, lim, bgs),
ThemeFamily.bauhaus => repairBauhausExtras(p, spec, lim, bgs),
_ => p,
};
if (extras == null) return null;
p = extras;
final repaired = spec.copyWith(palette: p);
violations = validateTheme(repaired, limits: lim);
return violations.isEmpty ? repaired : null;
}