high priority medium complexity infrastructure pending frontend specialist Tier 6

Acceptance Criteria

assert() blocks are present after ThemeData assembly for both light and dark variants — none in release mode (confirmed via kDebugMode or the assert keyword itself)
ContrastRatioValidator is called for all foreground/background color pairs defined in WCAG 2.2 AA scope: primary/onPrimary, secondary/onSecondary, tertiary/onTertiary, error/onError, surface/onSurface, surfaceVariant/onSurfaceVariant, primaryContainer/onPrimaryContainer, secondaryContainer/onSecondaryContainer, tertiaryContainer/onTertiaryContainer
AssertionError message format is: 'WCAG AA contrast violation: [tokenName] pair ([foregroundHex] on [backgroundHex]) = [ratio]:1, required 4.5:1 for normal text / 3.0:1 for large text'
Minimum touch target assertion checks that all button theme minimumSize values are >= Size(48, 48) and throws 'Touch target violation: [componentName] minimumSize [actual] is below WCAG 2.5.5 minimum 48×48 dp'
All assertions are gated on kDebugMode — no assert statement appears outside a kDebugMode check to prevent accidental production side effects in obscured builds
Running the app in debug mode with a known low-contrast theme triggers the AssertionError before the first frame is rendered
The ContrastRatioValidator implements the WCAG relative luminance formula (IEC 61966-2-1) and returns a double — unit tested against the W3C reference pairs
Zero performance impact in release/profile mode — confirmed via a profile build with no ContrastRatioValidator calls in the trace

Technical Requirements

frameworks
Flutter
Dart (assert keyword, kDebugMode)
apis
Flutter kDebugMode constant
ContrastRatioValidator (internal)
Flutter ThemeData color accessors
performance requirements
All validation logic runs only when kDebugMode is true — release builds have zero overhead
ContrastRatioValidator must complete all 9 color pair checks in under 1 ms on debug hardware
security requirements
AssertionError messages must not include PII or sensitive token values beyond hex color codes

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Integration Task

Handles integration between different epics or system components. Requires coordination across multiple development streams.

Implementation Notes

Implement ContrastRatioValidator as a pure static class with a single method: static double contrastRatio(Color foreground, Color background). The WCAG relative luminance formula requires linearising each sRGB channel: L = 0.2126*R + 0.7152*G + 0.0722*B where each channel is (c/255 <= 0.04045) ? c/255/12.92 : ((c/255+0.055)/1.055)^2.4. Contrast ratio = (L1+0.05)/(L2+0.05) where L1 is the lighter.

Wrap all assert() calls inside a single assertThemeCompliance(ThemeData theme, String variantLabel) function called once per variant (light/dark). The variantLabel ('light'/'dark') is included in the error message for fast diagnosis. Use Flutter's assert(condition, message) two-argument form for descriptive errors. Do not use debugPrint as a substitute — the assert must throw to halt rendering of a non-compliant theme during development.

This is especially critical for the Blindeforbundet (high visual impairment user base) and NHF (cognitive accessibility) user groups where contrast failures are non-negotiable.

Testing Requirements

Unit tests (flutter_test): (1) ContrastRatioValidator returns ≥ 4.5 for black on white (21:1 expected), (2) returns < 4.5 for #777777 on white (4.48:1), (3) assertThemeCompliance() throws AssertionError with correct message format for a low-contrast scheme, (4) assertThemeCompliance() passes silently for a WCAG-compliant scheme, (5) all 9 color pairs are validated in a single call, (6) touch target assertion triggers for a minimumSize of Size(40, 40). Test that the assertions are absent from a release-mode ThemeData by confirming ContrastRatioValidator is never instantiated outside a kDebugMode block (static analysis or grep test). 100% branch coverage on ContrastRatioValidator's luminance calculation path.

Component
Accessible Theme Builder
infrastructure medium
Epic Risks (2)
high impact low prob scope

One or more of the four partner organisations may supply brand primary colors that cannot be paired with any standard foreground at 4.5:1 contrast (for example, a mid-range hue that is too light for dark text and too dark for white text). Rejecting these colors programmatically could cause a political dispute with the organisation and delay the feature.

Mitigation & Contingency

Mitigation: Before implementation begins, run all four organisations' existing brand primary colors through the contrast-ratio-validator against both white (#FFFFFF) and a near-black (#1A1A1A). Share the results with each organisation's contact person ahead of the theme builder sprint so any problematic colors can be adjusted collaboratively with advance notice.

Contingency: If an organisation insists on a non-compliant brand color, produce a compliant near-match (lightened or darkened along the hue's luminance axis) and present both options with contrast ratio evidence. Document the adjusted token in the manifest with an explicit note that the original brand color was non-compliant, and obtain written sign-off from the organisation.

medium impact medium prob scope

Flutter's ThemeData contains over 30 component theme properties. If the theme-builder only addresses the most common ones (Button, InputDecoration, Card) and leaves others at Flutter defaults, downstream feature teams may unknowingly use default-themed widgets that do not meet sizing or contrast requirements.

Mitigation & Contingency

Mitigation: Produce a full inventory of all ThemeData component theme properties and map each to either a token-driven override or an explicit pass-through decision documented in the theme builder code. Prioritise the inventory by frequency of use in the existing codebase (identified via Grep). Include a check in the CI lint runner that flags widgets using Flutter default component themes not covered by the theme builder.

Contingency: If the full inventory scope exceeds the sprint budget, ship with the highest-frequency components covered and add a tracked backlog item for each uncovered component theme, pairing with a temporary lint suppression comment that includes the backlog reference.