Implement mark-as-read on view and CTA deep-link button
epic-scenario-push-engagement-ui-task-010 — On scenario-notification-detail-view screen mount, call scenario-notification-repository (579) to mark the notification record as read. Implement the primary CTA button that triggers scenario-deep-link-router (578) with the notification's scenario type and payload. Button must have minimum 48x48 pt touch target and clear accessible label. Handle loading and error states gracefully.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Call markAsRead in initState using ref.read (not ref.watch) to avoid re-triggering on rebuild. Use unawaited() explicitly and wrap in a try/catch to silence errors per the acceptance criteria. For the CTA, use an AsyncNotifier or local ValueNotifier
Use Semantics widget wrapping AppButton if AppButton does not natively support semanticLabel prop. Follow the existing AppButton widget API in the shared widget library; do not create a one-off button implementation. Keep the mark-as-read logic in the screen's initState rather than a side-effect in the Riverpod provider to maintain clear separation of concerns.
Testing Requirements
Write flutter_test widget tests covering: (1) markAsRead is called once on screen mount (verify via mock); (2) markAsRead failure does not prevent screen display or CTA interaction; (3) CTA button tap invokes scenario-deep-link-router with correct arguments; (4) CTA button is disabled and shows loading indicator during async router resolution; (5) router failure shows error snackbar; (6) CTA button is absent when payload is null. Mock both scenario-notification-repository and scenario-deep-link-router. Verify semanticLabel presence via SemanticsNode in widget tests. Integration test for the full banner→detail→deep-link path is handled in task-012.
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.
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.