critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

TokenAccessibilityEnforcer is a singleton service (or Riverpod provider) injectable across the app
assertColorPair(String foregroundToken, String backgroundToken) resolves token names to Color values from the token manifest and calls ContrastRatioValidator.validatePair; throws AssertionError in debug mode if ratio < 3.0
In profile mode, assertColorPair logs a structured warning via dart:developer log() with name='accessibility', level=900, and a JSON-encodable map containing token names and ratio
In release mode, assertColorPair is a no-op (compiled away via assert or kReleaseMode guard)
assertTextStyle(TextStyle style) throws AssertionError in debug mode if style.fontWeight is below FontWeight.w400 or style.fontStyle is FontStyle.italic
assertTouchTarget(Size size) throws AssertionError in debug mode if size.width < 44 or size.height < 44 (logical pixels)
All three assert methods accept an optional String context parameter included in the error message for traceability
Service does not make any network calls or async operations
Riverpod provider (tokenAccessibilityEnforcerProvider) exposes the service and is overridable in tests
Integration test verifies that a non-compliant color pair triggers AssertionError in debug mode

Technical Requirements

frameworks
Flutter
Dart (latest)
Riverpod
flutter_test
apis
dart:developer (log)
Flutter kDebugMode / kProfileMode / kReleaseMode constants
data models
ContrastResult (from task-004)
TokenManifest (color token name → Color mapping from task-002)
AccessibilityViolation (structured log payload: tokenForeground, tokenBackground, ratio, context)
performance requirements
All checks must be synchronous and complete in under 2ms
In release mode, all enforcement calls must be compiled to no-ops — zero runtime overhead
security requirements
Log output must not include PII — only token names and numeric values
AssertionError messages must be descriptive enough for debugging but safe for log aggregators

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Place in lib/core/accessibility/token_accessibility_enforcer.dart. Use a single final class with a factory constructor for the singleton pattern, or expose via a Riverpod Provider. The mode-branching pattern should use: assert(() { /* debug-only check */ return true; }()) for zero release overhead — the Dart compiler eliminates assert bodies in release builds. For profile mode warnings use: if (kProfileMode) { developer.log(...) }.

When resolving token names, fail fast with a clear message if the token name is not found in the manifest — a missing token is a configuration error, not an accessibility violation. The 44pt touch target minimum comes from Apple HIG and WCAG 2.5.5; document this in code comments. Consider making the minimum touch target size a configurable constant so it can be overridden for specific platforms if needed in the future.

Testing Requirements

Use flutter_test. Test file: test/token_accessibility_enforcer_test.dart. Required test cases: (1) compliant color pair does not throw in debug mode, (2) non-compliant pair (ratio < 3.0) throws AssertionError in debug mode, (3) non-compliant pair in profile mode logs warning and does not throw, (4) italic font style throws AssertionError, (5) FontWeight.w300 throws AssertionError, (6) FontWeight.w400 does not throw, (7) Size(40, 44) throws AssertionError for touch target, (8) Size(44, 44) does not throw. Use debugDefaultTargetPlatformOverride and TestWidgetsFlutterBinding to simulate debug/profile modes where needed.

Mock TokenManifest to control color resolution.

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.