critical priority low complexity frontend pending frontend specialist Tier 7

Acceptance Criteria

MaterialApp.theme and MaterialApp.darkTheme are both sourced exclusively from the ThemeBuilder Riverpod provider output — no hardcoded ThemeData anywhere in the root widget
Switching the active organisation context (NHF, Blindeforbundet, HLF, Barnekreftforeningen) causes the correct branding overrides to be applied within one frame with no hot restart required
Toggling light/dark mode via the app's mode switch causes the MaterialApp to rebuild with the correct ThemeData within one frame
The Riverpod provider is watched (not read) in the root widget so that any upstream state change triggers a reactive rebuild
No ThemeData construction logic exists outside the ThemeBuilder provider — all theme assembly is delegated
The widget tree below MaterialApp does not require any manual theme refresh or context invalidation after an org switch
App launches with the correct org theme on cold start, correctly restoring the last active organisation from persisted state
Theme integration does not regress existing accessibility properties: all ColorScheme values continue to meet WCAG 2.2 AA contrast requirements after wiring
No debug-mode assertions are thrown for missing theme tokens when the app starts with any of the four organisation configs
Performance: rebuilds triggered by theme changes are scoped to the subtree that consumes the theme — no full widget tree rebuilds beyond MaterialApp

Technical Requirements

frameworks
Flutter
Riverpod (ref.watch on ThemeBuilder provider in root widget)
BLoC (if org context is managed by a BLoC, bridge to Riverpod provider)
apis
Riverpod Provider/AsyncNotifierProvider for ThemeBuilder
Flutter ThemeData / ColorScheme APIs
MaterialApp theme / darkTheme properties
Flutter Brightness / MediaQuery for system dark mode detection
data models
OrganisationBrandingConfig (per-org token overrides)
ThemeBuilderState (assembled light + dark ThemeData pair)
ActiveOrganisationContext (currently selected org identifier)
performance requirements
Theme rebuild must complete within a single frame (< 16 ms on target devices)
Provider watch must not cause widget rebuilds on unrelated state changes
Cold start must resolve the persisted org context and theme before first paint
security requirements
Organisation context must be validated before applying branding overrides — no possibility of applying an unknown org's theme
Persisted org context must be read from secure/sandboxed local storage, not raw shared preferences without validation
ui components
Root MaterialApp widget (modified to consume provider)
ConsumerWidget or ConsumerStatefulWidget wrapper at app root
Organisation context switcher widget (triggers provider state change)

Execution Context

Execution Tier
Tier 7

Tier 7 - 84 tasks

Can start after Tier 6 completes

Integration Task

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

Implementation Notes

Use a ConsumerWidget (or HookConsumerWidget if the project uses flutter_hooks) at the app root rather than Consumer inside MaterialApp — this ensures the entire MaterialApp rebuilds when the theme changes, which is required for ThemeData to propagate correctly. Watch the ThemeBuilder provider with ref.watch, not ref.read, so reactive rebuilds are triggered. The org context should be a separate Riverpod provider (e.g., activeOrganisationProvider) that the ThemeBuilder provider depends on via ref.watch — this creates a clean reactive chain: org change → ThemeBuilder re-runs → MaterialApp rebuilds. Avoid storing ThemeData in BLoC state; keep it in Riverpod so it integrates cleanly with Flutter's Theme inheritance.

If the org context is currently in BLoC, create a bridge provider using StreamProvider that listens to the BLoC stream. Persist the active org ID using a secure local key (e.g., via flutter_secure_storage) and restore it in the provider's build method so the theme is correct on cold start without a frame of default theme. Be careful with MediaQuery for system brightness — use ref.watch(brightnessProvider) derived from PlatformDispatcher rather than reading MediaQuery inside the provider, which can cause context issues.

Testing Requirements

Unit tests: verify that the root widget reads ThemeBuilder provider output for both theme and darkTheme properties; verify that an org context change causes the provider to emit updated ThemeData. Widget tests: mount the root app with each of the four org configs and assert that Theme.of(context).colorScheme matches the expected values; simulate a light/dark toggle and assert the theme switches without rebuilding the full tree; simulate an org switch mid-session and assert the new branding is applied. Integration tests: cold-start the app with a persisted org context and verify the correct theme is applied before user interaction. Accessibility tests: run Flutter's SemanticsChecker after each theme change to confirm no accessibility regressions.

All tests must pass on both iOS and Android targets.

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.