Build contrast ratio validation API for runtime and CI use
epic-visual-design-accessibility-foundation-task-009 — Wrap the WCAG luminance algorithm in a ContrastRatioValidator service class exposing methods: validatePair(foreground, background) returning a ValidationResult with ratio, pass/fail status, and WCAG level (AA/AAA). Ensure the API is usable both at Flutter runtime and from Dart CLI scripts invoked by CI pipelines.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Define `TextSize` as an enum with `normal` and `large` values. The `WcagLevel` highestLevel field should be computed as: ratio >= AAA_threshold(textSize) ? WcagLevel.aaa : ratio >= AA_threshold(textSize) ? WcagLevel.aa : WcagLevel.fail.
Keep `ContrastRatioValidator` as a stateless class (no fields) so it can be used as a `const` singleton or injected via Riverpod as a plain provider (`Provider((ref) => const ContrastRatioValidator())`). The CLI entry point in `bin/` should parse hex strings like `#FFFFFF` or `FFFFFF` (with or without `#`), convert them to `Color` objects using `Color(int.parse('FF' + hex.replaceAll('#',''), radix: 16))`, and call validatePair. Print output in a machine-readable format (JSON) for CI parsing: `{"ratio": 21.0, "level": "aaa", "pass": true}`.
Testing Requirements
Write flutter_test unit tests for ContrastRatioValidator covering: (1) pair known to pass AA normal — assert passesAA=true, passesAAA=false or true as appropriate, (2) pair known to fail AA — assert passesAA=false, highestLevel=fail, (3) large text pair at 3.5:1 — passesAA=true when textSize=TextSize.large, passesAA=false when textSize=TextSize.normal, (4) AAA threshold boundary at exactly 7.0:1 — assert passesAAA=true, (5) summary string contains the ratio formatted to 2 decimal places. Also write a shell integration test for the CLI: run `dart bin/validate_contrast.dart FFFFFF 000000` and assert exit code 0; run with a failing pair and assert exit code 1.
The WCAG 2.2 relative luminance formula requires gamma-corrected sRGB calculations. Floating-point rounding differences between Dart and reference implementations could produce off-by-one classifications for near-threshold color pairs, resulting in pairs that just pass or just fail in CI but behave differently at runtime.
Mitigation & Contingency
Mitigation: Implement the algorithm directly from the WCAG 2.2 specification using the exact linearisation constants. Validate the Dart implementation against the W3C reference test vectors and against a known-good JavaScript implementation for at least 50 color pairs spanning the compliance boundaries.
Contingency: If discrepancies are found, add a configurable tolerance margin (e.g., ±0.005 on the ratio) and flag near-threshold pairs as warnings rather than hard failures, escalating to the design team for manual review.
The token manifest is a static data file. If developers add new color tokens to the design-token-provider without updating the manifest, the manifest becomes stale and the CI validator produces false negatives — passing builds that contain unvalidated color pairs.
Mitigation & Contingency
Mitigation: Add a CI step that cross-references every token constant exported by the design-token-provider against the manifest at build time, failing if any token is present in the provider but absent from the manifest. Document this requirement clearly in the contributing guide.
Contingency: If drift is detected post-merge, run a full manifest regeneration script and treat the resulting manifest diff as a blocking pull request with mandatory accessibility review.
The flutter_accessibility_lints package (or custom lint rules) may produce false positives on patterns the team deliberately uses — for example, decorative icon widgets that intentionally omit semantic labels. Excessive false positives will lead developers to add blanket ignore comments, undermining the entire lint strategy.
Mitigation & Contingency
Mitigation: Audit the full lint rule set against the existing codebase before enabling rules. Create a documented list of approved ignore-comment patterns with mandatory justification comments. Restrict ignore patterns to decorative-only contexts.
Contingency: If false positive rates exceed 10% of lint output, disable the highest-noise individual rules and replace them with targeted custom lint rules scoped to the specific patterns the team controls.