high priority medium complexity integration pending integration specialist Tier 1

Acceptance Criteria

A Riverpod StreamProvider subscribes to supabase-realtime-subscription-service (527) and emits a new ScenarioNotificationBannerData event each time a record is inserted into scenario_notifications for the current user's ID
The banner overlay is shown within 1 second of the Realtime INSERT event being received on the client
The Realtime subscription is filtered by user_id matching the authenticated user's JWT sub claim — no cross-user events are ever shown
When the app goes to the background and returns to the foreground, the subscription re-establishes if it was dropped; missed events during the background period are not retroactively shown as banners
Only INSERT events trigger the banner; UPDATE and DELETE events on scenario_notifications are silently ignored by this subscription
If the Realtime channel connection is lost (network error), the StreamProvider transitions to an error state but does not crash the app; the banner simply does not appear until reconnected
When the user is logged out or the session expires, the Realtime subscription is cancelled and the stream is closed
Only the notification payload fields (scenario_type, title, short_message) are extracted from the Realtime event; no sensitive PII fields are consumed by the banner layer
Simultaneous events (two inserts within 500ms) result in the second banner queuing after the first dismisses, not overwriting it

Technical Requirements

frameworks
Flutter
Riverpod (StreamProvider for the notification stream)
Supabase Realtime (websocket channel, INSERT filter on scenario_notifications table)
apis
supabase-realtime-subscription-service (527) — channel subscribe/unsubscribe methods
Supabase Auth — JWT sub claim for user_id filter
Supabase Realtime WebSocket — event payload parsing
data models
scenario_notifications table (scenario_type, title, short_message, user_id fields)
device_token (for contextual awareness of which device is subscribed)
performance requirements
Banner must appear within 1 second of server-side INSERT event
WebSocket connection must be maintained with heartbeat; reconnect within 5 seconds on network recovery
StreamProvider must not hold strong references that prevent widget tree garbage collection after logout
security requirements
Supabase Realtime RLS policies must be enforced server-side so that only the authenticated user's rows are broadcast — validate this with a test using a second user's session
JWT must be re-validated on every channel subscription — use the Supabase SDK's built-in auth header injection
Realtime payload must NOT contain sensitive PII fields (e.g., contact names, personnummer); validate the payload schema before rendering
Channel must be unsubscribed immediately on session invalidation or logout to prevent stale subscriptions
ui components
in-app-notification-banner (573, from task-004)
Overlay or global navigator key for inserting OverlayEntry above current route

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use supabase.channel('scenario-notifications-').on(PostgresChangeEvent.insert, filter: PostgresChangeFilter(type: FilterType.eq, column: 'user_id', value: currentUserId), ...) pattern via supabase-realtime-subscription-service (527) — do not create raw channels outside the service. Expose the stream as a StreamProvider that emits null initially. In the root widget (or a shell route), use a ConsumerWidget that watches the stream and calls an OverlayController to show/queue banners. Implement a simple BannerQueue using a List in a StateNotifier to handle simultaneous events.

Ensure the StreamProvider is kept alive (keepAlive: true) only while the user is authenticated — use ref.onDispose to cancel the subscription and ref.listen to react to auth state changes.

Testing Requirements

Write unit tests for the StreamProvider: mock supabase-realtime-subscription-service (527) to emit fake INSERT payloads and assert the provider emits corresponding ScenarioNotificationBannerData objects. Assert UPDATE events are filtered out. Assert that stream closes when the mock service is disposed. Write widget tests using a ProviderScope override: inject a fake stream that emits one event and assert the banner widget appears in the widget tree within one pump cycle.

Test the queue behaviour: emit two rapid events and assert the second banner appears only after the first is dismissed. Integration test (device or emulator): subscribe to a real Supabase local instance, INSERT a row, and confirm the banner appears — run as part of CI if a Supabase local dev environment is available. Verify via Supabase dashboard that RLS prevents cross-user event delivery.

Component
In-App Notification Banner
ui low
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.