high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Banner widget renders as an overlay at the top of the current screen using Flutter's Overlay or a Stack positioned widget; it does not shift existing screen content down
Banner displays: a leading icon (scenario-type-specific), a bold scenario title (max 1 line, ellipsis overflow), a short message (max 2 lines, ellipsis overflow), and a dismiss (X) icon button in the trailing position
All text and icon colours pass WCAG 2.2 AA contrast ratio (minimum 4.5:1 for normal text, 3:1 for large text and UI components) in both light and dark themes
Banner reads design tokens for background colour, text colour, icon colour, border radius, padding, and shadow — no hardcoded colour values anywhere in the widget
In light theme, background uses design token surface/elevated colour; in dark theme, uses the dark-mode equivalent token
Dismiss button is tappable with a minimum touch target of 44×44 dp per WCAG 2.2 AA
Widget is stateless at the scaffold level; all state (shown/hidden) is managed by a parent controller or Riverpod provider passed in
Widget accepts a plain data model (scenario title string, message string, icon key string) as constructor parameters — no direct service dependencies inside the widget
Renders correctly at font scales 1.0, 1.5, and 2.0 without overflow or clipping
Passes flutter_test golden snapshot test for light and dark themes at default font scale

Technical Requirements

frameworks
Flutter (Overlay, OverlayEntry, or Stack-based positioning)
Design token provider (existing project token system)
Riverpod (for wiring visibility state in subsequent tasks — widget itself is stateless)
performance requirements
Banner must appear within one frame (16ms) of being triggered — no async work inside the widget build method
Widget must not rebuild the entire screen; use a dedicated OverlayEntry or positioned Stack child
security requirements
Notification content rendered in the banner must be HTML-escaped or rendered as plain Text widgets to prevent injection via notification payloads
Icon keys must be resolved from a local asset map; never load remote images directly in the banner
ui components
Leading icon (Icon widget resolved from scenario type key)
Title Text widget (style: labelLarge or equivalent design token typography)
Message Text widget (style: bodySmall or equivalent design token typography)
Dismiss IconButton (Icons.close, minimum 44dp touch target)
Container with BoxDecoration using design token surface colour and border radius

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define a ScenarioNotificationBannerData value object (title, message, iconKey) to decouple the widget from any service types. Use AnimatedPositioned or SlideTransition for the enter/exit animation (slide down from top, 200ms ease-in-out) — this can be wired in this task even if the trigger logic comes in the next task, using a visible boolean parameter. Use Theme.of(context).extension() (or equivalent project pattern) to access design tokens — do not use Theme.of(context).colorScheme directly if the project wraps it in tokens. Keep the widget in its own file under lib/features/notifications/widgets/in_app_notification_banner.dart.

Do not place overlay management logic inside this widget — the widget only renders; a separate controller handles insertion/removal of the OverlayEntry.

Testing Requirements

Write widget tests: render banner with sample data in light theme and assert all four elements (icon, title, message, dismiss button) are present in the widget tree. Render in dark theme and assert background colour token differs. Test overflow: provide a 200-character title and assert it truncates with ellipsis and does not cause a RenderFlex overflow. Test dismiss button tap: assert onDismiss callback is called.

Write golden tests for light and dark themes at font scales 1.0 and 1.5. Manually verify WCAG contrast ratios using the Flutter DevTools accessibility checker or a colour contrast tool before marking the task complete.

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.