high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

Screen receives a notification ID via route arguments and passes it to the Riverpod provider; navigating to the screen without an ID shows a graceful error state
scenario-notification-repository (579) is called exactly once on screen mount via a FutureProvider or AsyncNotifier; subsequent rebuilds do not re-fetch unless explicitly invalidated
Scenario type label is rendered using the design-token typography system with text matching the enum-to-label mapping defined in the shared locale file
Human-readable trigger explanation text is displayed in full without truncation; long strings scroll within the layout without overflow
All three contextual data fields (days inactive since last activity, certification expiry date, session count) are rendered as labelled rows with label and value visually distinct
Days inactive field shows a formatted integer (e.g., '14 days'); certification expiry date is formatted as a localised date string (e.g., '12 Jun 2026'); session count is a formatted integer
When the notification record has null/missing contextual fields, the corresponding row renders a dash ('—') placeholder rather than crashing or displaying 'null'
All text elements pass WCAG 2.2 AA contrast ratio: minimum 4.5:1 for normal text, 3:1 for large text (≥18pt regular or ≥14pt bold), verified against the dark and light themes
Every tappable or interactive element has a minimum touch target of 44×44 pt as per iOS HIG and WCAG 2.5.5
Loading state shows a skeleton or CircularProgressIndicator; error state shows a localised error message with a retry action
Screen passes Flutter widget tests for all three data states: loading, success with full data, and success with partial/null contextual fields

Technical Requirements

frameworks
Flutter
Riverpod
apis
scenario-notification-repository (component 579) — getNotificationById(id)
data models
ScenarioNotification
ScenarioType (enum)
NotificationContextualData (daysInactive, certificationExpiryDate, sessionCount)
performance requirements
Initial data load must complete and render within 2 seconds on a mid-range device over a 4G connection
Provider must be scoped to the route so it is disposed on pop, preventing memory leaks across multiple detail navigations
security requirements
Notification ID passed via route must be validated as a non-empty UUID before the repository call; reject malformed IDs with an error state
No sensitive PII beyond what is stored in the notification record itself may be fetched or logged at this layer
ui components
LabelledDataRow widget (label + value layout, supports dash placeholder)
ScenarioTypeBadge widget (enum-to-display-label mapping)
AsyncStateWrapper (loading / error / data switcher)
Design-token text styles (bodyMedium, labelSmall, titleMedium)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use a FutureProvider.family keyed on the notification ID so the provider is automatically scoped and disposed with the route. Avoid using StateNotifier for a pure load-once read; FutureProvider.family is simpler and sufficient. The LabelledDataRow widget should accept a nullable String value and render '—' when null — centralising this logic prevents scattered null checks across the screen. Format dates using the intl package's DateFormat with the device locale; do not hardcode locale strings.

Trigger explanation text copy should live in the ARB localisation file, keyed by scenario type enum value, so it is translatable. Apply design tokens for all colours and spacing — do not use hardcoded hex values. Ensure the Riverpod provider ref.watch call is inside the build method, not initState, to correctly respond to provider state changes.

Testing Requirements

Write flutter_test widget tests covering: (1) loading state renders CircularProgressIndicator; (2) success state with full data renders scenario type label, trigger text, and all three contextual rows with correct formatted values; (3) success state with null contextual fields renders '—' placeholders without exceptions; (4) error state renders error message and retry button; (5) provider is called once on mount and not re-called on hot rebuild. Use mockito or mocktail to mock scenario-notification-repository. Verify contrast compliance using Flutter's SemanticsNode tree in widget tests where possible. No integration tests required at this subtask level — covered in task-012.

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.