Accessibility Design Token Enforcer setup
epic-cognitive-accessibility-foundation-task-005 — Implement the AccessibilityDesignTokenEnforcer infrastructure component. Define a validated set of accessibility-compliant design tokens: minimum touch target size (48×48dp), minimum contrast ratios (WCAG 2.2 AA), font scale constraints, and line-height rules. Build a token validation utility that throws assertion errors in debug mode when a widget violates defined token constraints. Integrate with the existing design-token-theme.
Acceptance Criteria
Technical Requirements
Implementation Notes
Implement the WCAG relative luminance formula from scratch in pure Dart — it is a well-defined algorithm (linearize sRGB values, then L = 0.2126R + 0.7152G + 0.0722B) and has no dependency. Contrast ratio = (L1 + 0.05) / (L2 + 0.05) where L1 is the lighter luminance. Guard ALL assertion method bodies with `if (!kDebugMode) return;` as the first line — this is more reliable than using assert() which can be compiled away depending on build configuration. For the AccessibilityTokenEnforcerWidget, use LayoutBuilder to get the rendered size of the child and compare against minTouchTarget — trigger the assertion in the layout phase, not paint phase, so the error is caught early.
To avoid false positives on decorative non-interactive widgets, require consumers to explicitly opt in by wrapping interactive widgets, rather than globally wrapping everything. The ContrastSafeColorPalette should be co-located with the existing design token theme file — add it as a static extension or a nested class, not a separate file, to make the relationship clear. Keep the token constants aligned with the AccessibilityTokens used in task-011 tests — these two tasks should reference the same source constants. Once this component exists, the CertificationStatusScreen audit (task-011) should import and use assertContrastRatio() in its tests.
Testing Requirements
Unit tests for contrast math: write golden-value tests for assertContrastRatio() using known WCAG examples — e.g., pure black on pure white = 21:1 (passes), mid-gray #767676 on white = 4.48:1 (barely passes AA normal text), light gray #AAAAAA on white = 2.32:1 (fails). Assert these exact values to lock in the luminance formula. Unit tests for assertion behavior: in debug mode, assert that assertTouchTarget(Size(40, 40)) throws a FlutterError; assert that assertTouchTarget(Size(48, 48)) does not throw. In release mode simulation (override kDebugMode with a test flag or use conditional compilation), assert that the same assertTouchTarget(Size(40, 40)) call does not throw.
Integration tests: place AccessibilityTokenEnforcerWidget around a button with a known-small touch target in a widget test and verify the FlutterError is thrown. ContrastSafeColorPalette tests: assert every named color pair in the palette passes its corresponding contrast ratio assertion — these tests serve as a regression guard against theme changes breaking contrast compliance. Run these tests in CI.
The error message registry and help content registry both depend on bundled JSON assets loaded at startup. If asset loading fails silently (e.g. malformed JSON, missing pubspec asset declaration), the entire plain-language layer falls back to empty strings or raw error codes, breaking the accessibility guarantee app-wide.
Mitigation & Contingency
Mitigation: Implement eager validation of both assets during app initialisation with an assertion failure in debug mode and a structured error log in release mode. Add integration tests that verify asset loading in the Flutter test harness on every CI run.
Contingency: Ship a hardcoded minimum-viable fallback message set directly in Dart code so the app always has at least a safe generic message, preventing a blank or code-only error surface.
The AccessibilityDesignTokenEnforcer relies on dart_code_metrics custom lint rules. If the lint toolchain is not already configured in the project's CI pipeline, integrating a new linting plugin may cause unexpected build failures or require significant CI configuration work beyond the estimated scope.
Mitigation & Contingency
Mitigation: Audit the existing dart_code_metrics configuration in the project before starting implementation. Scope the lint rules to a separate Dart package that can be integrated incrementally, starting with the most critical rule (hard-coded colors) and adding others in subsequent iterations.
Contingency: Fall back to Flutter test-level assertions (using the cognitive-accessibility-audit utility) to catch violations in CI if the lint plugin integration is delayed, preserving enforcement coverage without blocking the epic.
WizardDraftRepository must choose between shared_preferences and Hive for local persistence. Choosing the wrong store for the data volume (e.g. shared_preferences for complex nested wizard state) can lead to serialisation bugs or performance degradation, particularly on lower-end Android devices used by some NHF members.
Mitigation & Contingency
Mitigation: Define a clean repository interface first and implement shared_preferences as the initial backend. Profile serialisation round-trip time with a realistic wizard state payload (≈10 fields) before committing to either store.
Contingency: Swap the persistence backend behind the repository interface without touching wizard UI code, which is possible precisely because the repository abstraction isolates the storage detail.
The AccessibilityDesignTokenEnforcer scope could expand significantly if a large portion of existing widgets use hard-coded values. Discovering widespread violations during this epic would force either a major refactor or a decision to exclude legacy components, potentially reducing the enforcer's coverage and value.
Mitigation & Contingency
Mitigation: Run a preliminary audit of existing widgets using a simple grep for hard-coded hex colors and raw pixel values before implementation begins. Use the results to set a realistic remediation boundary for this epic and log all out-of-scope violations as tracked tech-debt items.
Contingency: Scope the enforcer to new and modified components only (via file-path filters in dart_code_metrics config), shipping a partial but immediately valuable coverage rather than blocking the epic on full-codebase remediation.