critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

A Dart file lib/design_tokens/accessibility_token_manifest.dart is created containing a sealed class or abstract final class AccessibilityTokenManifest
The manifest declares a static const double minContrastRatio = 4.5 matching WCAG 2.2 AA for normal text
The manifest declares a static const double largeTextContrastRatio = 3.0 for text >= 18pt or >= 14pt bold
The manifest declares a static const FontWeight minFontWeight = FontWeight.w400
The manifest declares a static const double minTouchTargetPt = 44.0 matching Apple HIG and WCAG 2.5.5
The manifest declares an exhaustive list of approved color pairs as static const List<ApprovedColorPair> approvedColorPairs, where each pair contains foreground, background, and verified contrast ratio
The manifest is fully const-constructible — no factory methods, no mutable state, no late fields
A companion unit test file verifies every constant has its expected value and no approved color pair has a stored contrast ratio below minContrastRatio
The file has no external package dependencies beyond dart:ui (Flutter's core color types)
dart analyze reports zero issues on the file

Technical Requirements

frameworks
Flutter
Dart
data models
AccessibilityTokenManifest
ApprovedColorPair
performance requirements
All constants must be compile-time constants (const) — zero runtime allocation for manifest access
Manifest file must not trigger any static analysis warnings
security requirements
No personally identifiable information or organization-specific data in the manifest
Manifest must be read-only — enforce via const or final + private constructor

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Model the manifest as a pure data namespace, not a service. Use a final class with a const constructor and all static members, or a Dart extension type wrapping a const record. Define ApprovedColorPair as a simple const class with foreground (Color), background (Color), and contrastRatio (double) fields — no methods. Derive the approved color pairs from the project's design token color palette (primary, surface, error, etc.) and compute their contrast ratios offline using the WCAG relative luminance formula before committing — do not compute at runtime.

This is intentional: the manifest is a pre-validated allowlist, not a dynamic checker. The rationale: NHF users include individuals with cognitive impairments and Blindeforbundet users depend on high-contrast for screen reader usability — any color combination used in the app must be pre-approved, not opportunistically checked. Name constants with SCREAMING_SNAKE_CASE per Dart style guide to make their compile-time nature visually obvious.

Testing Requirements

Unit tests using flutter_test. Test groups: (1) constant values — assert each constant equals its expected literal value; (2) color pair coverage — assert approvedColorPairs is non-empty and each entry's storedContrastRatio >= 4.5; (3) immutability — assert the class cannot be instantiated (private constructor or abstract) and has no settable fields; (4) no-dependency check — assert the import list contains only dart:ui and package:flutter/material.dart. Run with flutter test --coverage and require 100% coverage on the manifest file.

Component
Token Accessibility Enforcer
service medium
Epic Risks (4)
medium impact high prob integration

Flutter's textScaleFactor behaviour differs between iOS and Android, and third-party widgets used across the app (date pickers, bottom sheets, chips) may not respect the per-role scale caps applied by the dynamic-type-scale-service, causing overflow in screens this epic cannot directly control.

Mitigation & Contingency

Mitigation: Enumerate all third-party widget usages that render text. For each, verify whether they honour the inherited DefaultTextStyle and MediaQuery.textScaleFactor or use hardcoded sizes. File issues with upstream packages and wrap non-compliant widgets in MediaQuery overrides scoped to the safe cap for that role.

Contingency: If upstream packages cannot be patched within the sprint, implement a global MediaQuery wrapper at the app root that clamps textScaleFactor to the highest per-role safe value (typically 1.6–2.0), accepting that users at extreme OS scales see a safe cap rather than full scaling for those widgets.

high impact medium prob dependency

The CI accessibility lint runner depends on the Dart CLI toolchain and potentially custom_lint or a bespoke Dart script. CI environments differ from local dev environments in Dart SDK version, pub cache configuration, and platform availability, risking intermittent CI failures that block all pull requests.

Mitigation & Contingency

Mitigation: Pin the Dart SDK version in the CI workflow configuration. Package the lint runner as a self-contained Dart script with all dependencies vendored or declared in a dedicated pubspec.yaml. Add a CI smoke test that runs the runner against a known-compliant fixture and a known-violating fixture to verify the exit codes are correct.

Contingency: If the custom runner proves too fragile, fall back to running dart analyze with the flutter-accessibility-lint-config rules as the sole CI gate, and schedule the custom manifest validation as a separate non-blocking advisory check until the runner is stabilised.

medium impact medium prob technical

Wrapping all interactive widgets with a 44 pt minimum hit area via HitTestBehavior.opaque may cause unintended tap interception in widgets where interactive elements are closely stacked, particularly in the expense type selector, bulk confirmation screen, and notification filter bar.

Mitigation & Contingency

Mitigation: Conduct integration testing of the touch target wrapper specifically in dense layout scenarios (expense selector, filter bars, bottom sheets with multiple buttons). Use the Flutter Inspector to visualise hit areas and confirm no overlaps. Pair with the interactive-control-spacing-system to ensure minimum 8 dp gaps between expanded hit areas.

Contingency: If overlapping hit areas cause mis-tap regressions in specific screens, allow the touch target wrapper to accept an explicit hitAreaSize parameter that can be reduced below 44 pt only in contexts where the interactive-control-spacing-system guarantees sufficient gap, with a mandatory code review flag for any such override.

high impact medium prob scope

The contrast-safe-color-palette must guarantee WCAG AA ratios for both light and dark mode token sets. Dark mode color derivation is non-trivial — simply inverting a light palette often produces pairs that pass in one mode but fail in the other, and the token manifest must encode both sets explicitly.

Mitigation & Contingency

Mitigation: Define both light and dark token sets explicitly in the accessibility-token-manifest rather than deriving one from the other programmatically. Run the contrast-ratio-validator against both sets as part of the token manifest generation process and include both in the CI lint runner's validation scope.

Contingency: If time pressure forces a dark mode deferral, ship with light mode only and add a prominent in-app notice. Gate dark mode colour tokens behind a feature flag until the full dual-palette validation is complete.