high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Screen renders a page header with a back button and the localised title 'Notification Details' (or equivalent project label key)
A scenario type label section displays a styled badge or chip indicating the scenario type (e.g., 'Inactivity Reminder', 'Certification Expiry') using the project's entity colour system
A trigger explanation section displays a human-readable sentence explaining why the notification was triggered (e.g., 'You have not logged any activity in the past 14 days')
A contextual data section displays up to three key-value rows with design token typography: days inactive (integer), certification expiry date (formatted as locale-aware date string), and session count (integer) — rows for unavailable data are hidden rather than showing 'null' or 'N/A'
A primary CTA button placeholder renders at the bottom of the screen with label 'Take Action' (placeholder text to be replaced in a subsequent task); the button is visually styled using the project's AppButton component with primary variant
All sections use design token spacing (padding, margin, gap) — no hardcoded pixel values
Screen is read-only: no editable fields, no inline form elements
Screen is reachable via a named route (e.g., /notifications/scenario/:id) and accepts a ScenarioNotificationDetailArgs parameter via GoRouter's extra or path param
Screen renders without errors when contextual data fields are null or missing (graceful degradation)
All text content passes WCAG 2.2 AA contrast requirements in both light and dark themes
Scrollable layout handles long trigger explanation text without overflow on small screens (375dp width)

Technical Requirements

frameworks
Flutter (StatelessWidget or ConsumerWidget if a Riverpod provider is needed for data fetch)
Riverpod (FutureProvider if notification detail must be fetched from Supabase by ID)
GoRouter (named route registration and args passing)
Design token provider (existing project token system)
apis
Supabase PostgreSQL 15 — read scenario_notifications record by ID if detail data is not passed via args
scenario-deep-link-router (578) — for CTA button navigation in subsequent tasks
data models
scenario_notifications (scenario_type, trigger_explanation, days_inactive, certification_expiry_date, session_count fields)
certification (cert_type, issued_at, expires_at — for displaying expiry context)
performance requirements
Screen must render initial scaffold within one frame of navigation; data fetch (if any) shows a skeleton loader
Contextual data rows must not cause layout jank; use const constructors for static row widgets
security requirements
Screen must only display notification records belonging to the authenticated user — validate user_id match before rendering
All string content from the notification payload must be rendered via Flutter Text widgets (not HTML/webview) to prevent injection
Route must not be accessible without authentication — wrap in an auth guard at the router level
ui components
Page header widget with back button (existing project AppPageHeader or equivalent)
Scenario type badge/chip (styled with entity colour system, badge-story or equivalent token)
Trigger explanation Text widget (bodyLarge design token typography)
Contextual data KeyValueRow widgets (3 instances, conditionally rendered)
AppButton (primary variant, full-width, CTA placeholder at bottom)
SingleChildScrollView wrapper for the full body content

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define a ScenarioNotificationDetailArgs data class (scenario_type, trigger_explanation, days_inactive int?, certification_expiry_date DateTime?, session_count int?) to type-safe the route arguments. Pass the full args object via GoRouter's extra field to avoid URI-encoding complex data. If the notification detail must be fetched from Supabase (args only contains an ID), use a FutureProvider keyed on the notification ID and show a shimmer skeleton while loading. Use the project's KeyValueRow or equivalent widget for the contextual data section — if none exists, create a simple private _DetailRow widget within this screen file.

The CTA button should fire a no-op onTap in this scaffold task; wire real navigation in the subsequent epic task. Register the route in the project's GoRouter configuration file under the authenticated shell route so the auth guard applies automatically.

Testing Requirements

Write widget tests: render the screen with a full ScenarioNotificationDetailArgs object and assert all five sections are present in the widget tree. Render with null days_inactive and assert the days-inactive row is absent. Render with all contextual data null and assert no 'null' text is visible anywhere. Test that the CTA button is present and tappable (onTap fires without error in placeholder state).

Test the route: navigate to /notifications/scenario/test-id via GoRouter test harness and assert the screen is pushed. Golden test for light and dark themes. Manual accessibility check: run Flutter DevTools Accessibility inspector and confirm no contrast violations on both themes.

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.