repairTheme function

ThemeSpec? repairTheme(
  1. ThemeSpec spec, {
  2. GuardrailLimits? limits,
})

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;
}