Files
JesseMarkowitz 87a40326a2 v1.1: harden recovery and control boundaries
WP-D and WP-E complete the planned v1.1 implementation packages.

WP-D — recovery honesty:
- backups verify the completed copy with PRAGMA integrity_check
- corruption missed by quick_check is detected by the full check
- existing good backups remain protected
- oversized exports are still delivered but declare whether this version can
  import them, while the 20 MB import limit remains unchanged
- backup was exercised through the real browser UI on both the normal campaign
  database and a campaign-shaped database over 100 MB

WP-E — control-boundary contrast:
- interactive control boundaries meet the WCAG 1.4.11 3:1 target
- the contrast audit is now a failing gate rather than an advisory
- rendered browser measurements pass for the composer, controls, tabs and nav
- text contrast and focus visibility remain intact
- owner reviewed and approved the before/after screenshots

Reports:
- planning/reports/v1.1/V1.1-WP-D-REPORT.md
- planning/reports/v1.1/V1.1-WP-E-REPORT.md

All planned v1.1 work packages A-E are now complete. Release validation has not
yet begun.
2026-09-16 05:37:13 -04:00

153 lines
6.7 KiB
Python

"""v1.1 WP-E: the contrast audit is a gate, not a report.
Before this package `tools/contrast_audit.py` measured control boundaries,
printed that two of them were below 3:1, and exited 0 anyway — on the argument
that a control is identified by its label rather than its edge. A check that
cannot fail is not a check, and these tests are what make it one: the threshold
is exercised from both sides, on a real tokens file, so a future palette change
that dims a control's edge stops the run instead of adding a line to it.
python -m pytest tests/test_v11_e_contrast.py -v
"""
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "tools"))
import contrast_audit as audit # noqa: E402
# --------------------------------------------------------------- the maths
def test_the_ratio_is_the_wcag_ratio():
"""Anchored on values with known answers, so a broken formula is visible."""
assert audit.ratio("#ffffff", "#000000") == pytest.approx(21.0, abs=0.01)
assert audit.ratio("#ffffff", "#ffffff") == pytest.approx(1.0, abs=0.001)
# Order does not matter: contrast is symmetric.
assert audit.ratio("#131320", "#676792") == pytest.approx(
audit.ratio("#676792", "#131320"), abs=1e-9)
# ------------------------------------------------------- the gate itself
def tokens_file(tmp_path: Path, **overrides: str) -> Path:
"""A real tokens file with named colours replaced."""
source = audit.TOKENS.read_text()
for name, value in overrides.items():
token = "--" + name.replace("_", "-")
start = source.index(f"{token}: ")
end = source.index(";", start)
source = source[:start] + f"{token}: {value}" + source[end:]
written = tmp_path / "tokens.css"
written.write_text(source)
return written
def run_against(path: Path, monkeypatch) -> int:
monkeypatch.setattr(audit, "TOKENS", path)
return audit.main()
def test_a_boundary_below_three_to_one_fails_the_run(tmp_path, monkeypatch, capsys):
"""2.99:1 against --bg-input — the wrong side of the line by one hundredth.
This value clears 3:1 against --bg-panel (3.21:1), so it would have passed
the audit as M11 wrote it. It fails now because the floor is taken against
the background the control is actually drawn on.
"""
below = tokens_file(tmp_path, border="#58639a")
assert run_against(below, monkeypatch) == 1
printed = capsys.readouterr().out
assert "2.99:1" in printed
assert "FAIL" in printed
assert "below 3:1 (WCAG 1.4.11)" in printed
def test_a_boundary_at_three_to_one_passes(tmp_path, monkeypatch, capsys):
"""3.00:1 — the right side of the same line, one hundredth from the value
above, and not failed for arithmetic the reader cannot see."""
at = tokens_file(tmp_path, border="#58639b")
assert run_against(at, monkeypatch) == 0
printed = capsys.readouterr().out
assert "3.00:1" in printed
assert "every control boundary clears 3:1" in printed
def test_the_v1_0_0_boundary_would_now_fail(tmp_path, monkeypatch, capsys):
"""The value v1.0.0 shipped. This is the defect WP-E closes, and the gate
has to be the thing that would have caught it."""
shipped = tokens_file(tmp_path, border="#2b2b3d")
assert run_against(shipped, monkeypatch) == 1
assert "1.24:1" in capsys.readouterr().out # against --bg-input, the worst case
def test_a_text_pair_below_four_point_five_still_fails(tmp_path, monkeypatch, capsys):
"""WP-E raised the boundary floor and must not have lowered the text one."""
dimmed = tokens_file(tmp_path, text_dim="#5a5750")
assert run_against(dimmed, monkeypatch) == 1
assert "below WCAG AA (1.4.3)" in capsys.readouterr().out
def test_a_missing_token_is_a_failure_not_a_skip(tmp_path, monkeypatch, capsys):
source = audit.TOKENS.read_text().replace("--border-bright:", "--border-was-renamed:")
written = tmp_path / "tokens.css"
written.write_text(source)
assert run_against(written, monkeypatch) == 1
assert "MISSING TOKEN" in capsys.readouterr().out
# ------------------------------------------------- the shipped palette
def test_the_real_tokens_pass_both_criteria(capsys):
"""The palette as it stands, through the same gate CI would run."""
assert audit.main() == 0
printed = capsys.readouterr().out
assert "every text pair clears WCAG AA (1.4.3)" in printed
assert "every control boundary clears 3:1 (1.4.11)" in printed
assert "FAIL" not in printed
def test_every_boundary_pair_is_measured_against_the_background_it_is_drawn_on():
"""The audit used to check borders only against --bg-panel, which is not
where the bordered controls are: inputs and buttons sit on --bg-input
(styles/forms.css), which is lighter and therefore harder. Checking only the
easier background would let a token pass while the real control failed."""
boundary = [(fg, bg) for kind, fg, bg, _, _ in audit.PAIRS if kind == "boundary"]
for background in ("--bg-input", "--bg-panel", "--bg"):
assert ("--border", background) in boundary, background
assert ("--border-bright", "--bg-input") in boundary
def test_the_text_baselines_are_unchanged_by_wp_e():
"""WP-E changed only boundary tokens. These are the M11 text measurements,
and they have to still be exactly what the earlier reports recorded."""
tokens = audit.read_tokens(audit.TOKENS)
measured = {
"body text on the page": audit.ratio(tokens["--text"], tokens["--bg"]),
"body text in a panel": audit.ratio(tokens["--text"], tokens["--bg-panel"]),
"secondary text in a panel": audit.ratio(tokens["--text-dim"], tokens["--bg-panel"]),
"secondary text on the page": audit.ratio(tokens["--text-dim"], tokens["--bg"]),
}
assert measured["body text on the page"] == pytest.approx(14.57, abs=0.01)
assert measured["body text in a panel"] == pytest.approx(13.57, abs=0.01)
assert measured["secondary text in a panel"] == pytest.approx(5.48, abs=0.01)
assert measured["secondary text on the page"] == pytest.approx(5.88, abs=0.01)
def test_the_hover_edge_stays_brighter_than_the_resting_edge():
"""Rest and hover have to remain distinguishable from each other, not merely
each clear the floor against the background."""
tokens = audit.read_tokens(audit.TOKENS)
panel = tokens["--bg-panel"]
assert audit.ratio(tokens["--border-bright"], panel) > audit.ratio(tokens["--border"], panel)
def test_a_boundary_never_becomes_as_loud_as_body_text():
"""A control's edge that outshines the words inside it is its own defect."""
tokens = audit.read_tokens(audit.TOKENS)
panel = tokens["--bg-panel"]
assert audit.ratio(tokens["--border-bright"], panel) < audit.ratio(tokens["--text"], panel)