critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

A DesignTokens immutable data class is defined with typed fields: colors (DesignColorTokens), typography (DesignTypographyTokens), spacing (DesignSpacingTokens)
A Riverpod Provider<DesignTokens> named designTokenProvider is defined and accessible from any widget that has ProviderScope in its tree
DesignColorTokens exposes at minimum: primary, onPrimary, surface, onSurface, error, onError — all as Flutter Color values derived from AccessibilityTokenManifest approved pairs
DesignTypographyTokens exposes at minimum: bodyLarge, bodyMedium, labelLarge TextStyle instances, each with fontWeight >= FontWeight.w400 and no fontStyle italic
DesignSpacingTokens exposes at minimum: xs (4.0), sm (8.0), md (16.0), lg (24.0), xl (40.0) as const double values
All token values are traceable to constants in AccessibilityTokenManifest — no magic numbers in the provider implementation
The provider is a Provider (not StateProvider or StateNotifierProvider) — it returns an immutable DesignTokens instance
Consuming a token in a widget requires only ref.watch(designTokenProvider).colors.primary — no Theme.of(context) fallback needed for the token surface
flutter_test unit tests confirm provider is readable in a ProviderContainer and returns expected typed values
dart analyze reports zero issues across all provider files

Technical Requirements

frameworks
Flutter
Riverpod
Dart
data models
DesignTokens
DesignColorTokens
DesignTypographyTokens
DesignSpacingTokens
AccessibilityTokenManifest
performance requirements
Provider must be a compile-time constant or eagerly initialized singleton — zero lazy computation on first read
DesignTokens instance must be const-constructible to enable tree-shaking of unused token values
Provider read latency must be imperceptible (<1ms) — no I/O or async initialization
security requirements
Provider must not expose any mutable state that downstream widgets could corrupt
No platform channel calls, no network access — purely in-memory compile-time constants

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Structure the implementation as a package-internal library with three files: design_tokens.dart (barrel export), design_token_models.dart (DesignTokens, DesignColorTokens, DesignTypographyTokens, DesignSpacingTokens data classes), design_token_provider.dart (Riverpod provider definition). All model classes should use @immutable annotation and const constructors. The provider itself should be a simple top-level final: final designTokenProvider = Provider((_) => const DesignTokens(...));. This enables Riverpod's auto-dispose detection and makes the provider effectively a glorified constant with the dependency injection benefits of the Riverpod graph.

Avoid extending Flutter's ThemeData or ColorScheme — the design token system is intentionally decoupled from Flutter's theme system to prevent accidental overrides via Theme.of(context). However, provide a DesignTokens.toColorScheme() and DesignTokens.toTextTheme() extension methods that downstream ThemeData configuration can use, keeping the bridge explicit.

This architecture supports the multi-organization requirement: each organization (NHF, Blindeforbundet, HLF, Barnekreftforeningen) may eventually require its own token variant — the Provider abstraction makes this substitutable without touching widget code.

Testing Requirements

Unit and widget tests using flutter_test and Riverpod's ProviderContainer. Test groups: (1) provider instantiation — create a bare ProviderContainer and assert designTokenProvider returns a non-null DesignTokens; (2) color token values — assert each color field matches its AccessibilityTokenManifest-derived expected value; (3) typography compliance — assert every TextStyle in DesignTypographyTokens has fontWeight >= FontWeight.w400 and fontStyle != FontStyle.italic; (4) spacing values — assert all spacing constants match expected pixel values; (5) widget tree integration — mount a minimal widget with ProviderScope, consume designTokenProvider, assert token values applied to a Text widget are reflected in the rendered widget tree. Achieve 100% line coverage on all DesignToken* classes.

Component
Dynamic Type Scale Service
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.