high priority low complexity integration pending frontend specialist Tier 2

Acceptance Criteria

Screen mounts and reads current preference state from notification-preference-service (577) via a Riverpod provider; all toggles reflect persisted values within 300ms of screen open
Toggling any preference calls the service to persist immediately; the service write completes before the toggle animation finishes
If the service write fails, the toggle reverts to its previous state and a snackbar error message is shown to the user
Subsequent app launches and re-entries to the screen always show the last persisted state, not a default hardcoded value
No preference change is written to the service until the user actually interacts with a toggle (no spurious writes on mount)
Screen handles the case where the service returns null or empty preferences by falling back to sensible defaults and still persisting on first toggle
Loading state is shown (e.g., shimmer or disabled toggles) while the initial preference fetch is in progress
All Riverpod providers involved are properly scoped and disposed when the screen is removed from the widget tree

Technical Requirements

frameworks
Flutter
Riverpod (StateNotifierProvider or AsyncNotifierProvider for preference state)
BLoC (if existing screen state management uses BLoC — adapt to co-exist)
apis
notification-preference-service (577) — read and write preference methods
Supabase PostgreSQL 15 — underlying persistence layer accessed via service
data models
accessibility_preferences (for user-level toggle storage pattern reference)
device_token (may inform which device preferences apply to)
performance requirements
Initial preference load must complete within 500ms on a standard connection
Toggle-to-persist round trip must not block the UI thread; use async write with optimistic UI update
Provider must cache preferences in memory to avoid redundant reads within the same session
security requirements
Preferences must be scoped to the authenticated user's ID; never read or write another user's preferences
JWT from Supabase Auth must be included in all service calls; unauthenticated calls must be rejected
No preference data written to local storage without encryption (use flutter_secure_storage pattern if caching locally)
ui components
Toggle controls (existing, from prior task)
Loading shimmer or skeleton placeholders for toggles during fetch
Snackbar or error banner for write failure feedback

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use AsyncNotifierProvider so the initial load is naturally async and the loading/error/data states are handled by Riverpod's built-in AsyncValue. Implement optimistic update: update local state immediately on toggle, then call the service; if the service throws, revert via state = previousState. Override the service with a Riverpod override in tests — avoid static singletons. Ensure the provider is placed above the screen in the widget tree (e.g., in the router's provider scope) so it survives minor navigations but is disposed on full logout.

Do not call the service multiple times if the user toggles rapidly — debounce writes by 300ms or cancel the previous pending write.

Testing Requirements

Write unit tests for the Riverpod notifier: verify initial state is loaded from the service mock, verify toggle mutations call the correct service method with correct arguments, verify revert-on-failure logic. Write widget tests for the preferences screen: mount with a mocked service returning known preferences, assert toggles render in correct on/off state, simulate a toggle tap and assert the service mock was called, simulate a service write failure and assert the toggle reverts and error UI appears. No e2e test required for this task — covered by integration test at the epic level. Target 90%+ branch coverage on the notifier class.

Epic Risks (2)
medium impact medium prob technical

The in-app notification banner depends on a Supabase Realtime subscription to detect new notification records. If the subscription reconnects slowly after an app resume from background, or if Realtime delivery is delayed under high load, the banner may not appear within the 2-second acceptance criterion.

Mitigation & Contingency

Mitigation: Implement an explicit subscription reconnect handler on app foreground events using Flutter's AppLifecycleState.resumed hook, and add a polling fallback that queries for unread notifications once per app foreground event as a safety net against missed Realtime events.

Contingency: If Realtime proves unreliable in production, promote the polling fallback to the primary mechanism with a 30-second interval, accepting slight latency in exchange for reliability.

medium impact medium prob technical

Cold-start deep linking (app not running when push notification is tapped) requires deferred navigation after the Flutter engine and Supabase session are fully initialised. If the deep link is consumed before authentication completes, the router may navigate to a protected route without a valid session, causing an error or redirect loop.

Mitigation & Contingency

Mitigation: Implement a deferred navigation queue in scenario-deep-link-router that holds the parsed deep-link target until the auth session restoration lifecycle event fires, following the existing deep-link-handler pattern used in the BankID and Vipps authentication flows.

Contingency: If deferred navigation is not achievable within the epic's scope, fall back to navigating the user to the notification centre (which is always accessible post-login) where the relevant notification record is visible and tappable.