critical priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

A ThemeBuildResult immutable value object containing lightTheme: ThemeData and darkTheme: ThemeData is returned by the builder — never two separate provider calls
ThemeData.useMaterial3 is set to true on both variants
A Riverpod Provider<ThemeBuildResult> (or AsyncNotifierProvider if token loading is async) is registered and consumed by MaterialApp.theme and MaterialApp.darkTheme
Changing the accessibility_preferences contrast_mode for the active user invalidates the Riverpod provider and MaterialApp re-renders with updated themes within one frame
Changing the accessibility_preferences font_scale_factor invalidates the provider and the type scale updates without hot restart
ThemeData.extensions is populated with any custom ThemeExtension instances (e.g., AppColorExtension for brand-specific extra colors) — no app-level code accesses DesignTokenProvider directly for color resolution
Both ThemeData objects pass a structural equality check confirming colorScheme, textTheme, and all registered component themes are non-default
MaterialApp does not define inline ThemeData anywhere — all theme construction is delegated to the builder via the Riverpod provider

Technical Requirements

frameworks
Flutter
Material 3
Riverpod
apis
Flutter ThemeData API
Flutter ThemeData.extensions API
Riverpod Provider / NotifierProvider
MaterialApp theme/darkTheme props
data models
accessibility_preferences
performance requirements
ThemeData assembly must complete in under 16 ms to avoid dropped frames during theme switching
Riverpod provider must use select() at consumer sites to avoid unnecessary rebuilds when unrelated state changes
security requirements
ThemeBuildResult must be an immutable data class — no setters or mutable fields
Organisation branding config must not be stored in the Riverpod provider state beyond the build — discard after ThemeBuildResult is produced
ui components
MaterialApp
ThemeData
ThemeExtension subclasses

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Integration Task

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

Implementation Notes

Define ThemeBuildResult as a Dart record (Dart 3+): record ThemeBuildResult(ThemeData light, ThemeData dark). The Riverpod provider should watch both the token provider and the accessibility preferences provider using ref.watch so any change to either triggers a rebuild. Use a synchronousProvider if token resolution is synchronous (preferred) to avoid loading states in MaterialApp. Register custom ThemeExtension subclasses via ThemeData.extensions to expose brand colors (e.g., badge colors, status colors) that have no Material 3 ColorScheme counterpart — this prevents scattered DesignTokenProvider.instance calls throughout the widget tree.

Ensure the provider is placed above MaterialApp in the ProviderScope to avoid lookup errors.

Testing Requirements

Unit tests: (1) ThemeBuildResult contains non-null light and dark ThemeData, (2) both have useMaterial3 true, (3) provider invalidation produces a new ThemeBuildResult instance (not the cached one), (4) structural equality confirms colorScheme/textTheme/component themes are populated. Widget test: wrap a minimal MaterialApp with the Riverpod provider, change the mocked accessibility_preferences, and assert the theme color seen by a child widget changes within one pump cycle. Integration test: verify the full provider graph from accessibility_preferences → ThemeBuildResult → MaterialApp renders without errors. 90% coverage target on the builder and provider setup.

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.