#!/usr/bin/env bash # verify-colour-channels.sh — Block G of preflight. # # EnsureContrast works in ABGR. ThemeColors fields are RGBA. Feeding one into # the other swaps red and blue on both arguments, measures a contrast between # two colours that are never on screen, and returns a value in the wrong order # on top — and because RgbaToAbgr is an involution, a stray extra conversion # around the call makes it LOOK plausible while being wrong. # # This happened four times on 2026-08-19 alone (channel header, ghost buttons, # pill text, popup rows), each found only by a human staring at an unreadable # window. The compiler cannot catch it — both layouts are uint. This script # catches the one shape every occurrence shared: a raw ThemeColors member as a # direct argument to EnsureContrast. # # Legitimate calls convert first (RgbaToAbgr(...)) or pass values that are # already ABGR (fields/locals named *Abgr, palette.Abgr(...)). set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" fail() { echo "verify-colour-channels: FAIL — $1" >&2; exit 1; } ok() { echo "verify-colour-channels: OK — $1"; } HITS="$(python3 - "$ROOT/HellionChat" <<'PY' import re import sys from pathlib import Path root = Path(sys.argv[1]) # A ThemeColors member (via any local alias) fed straight into EnsureContrast, # in either argument position, spanning line breaks. Members already in ABGR # end in "Abgr" and are exempt. pattern = re.compile( r"EnsureContrast\(\s*" r"(?:[A-Za-z_][\w.]*\.)?(?:Colors|c|colors)\.(?!\w*Abgr\b)\w+\s*," r"|EnsureContrast\([^;]{0,200}?,\s*" r"(?:[A-Za-z_][\w.]*\.)?(?:Colors|c|colors)\.(?!\w*Abgr\b)\w+\s*[,)]", re.S, ) hits = [] for f in root.rglob("*.cs"): if "obj" in f.parts or "bin" in f.parts: continue text = f.read_text(encoding="utf-8") for m in pattern.finditer(text): line = text.count("\n", 0, m.start()) + 1 hits.append(f"{f.relative_to(root.parent)}:{line}") print("\n".join(hits)) PY )" if [ -n "$HITS" ]; then fail "raw ThemeColors member fed into EnsureContrast (expects ABGR — wrap in RgbaToAbgr): $HITS" fi ok "no raw RGBA theme members reach EnsureContrast"