high priority low complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

Tapping a NotificationListItem immediately dispatches MarkNotificationRead(notificationId) to the BLoC before any async navigation begins (optimistic update)
The tapped item's visual state updates synchronously: the left accent border disappears and the title weight changes from bold to normal within the same frame as the tap
NotificationDeepLinkHandler.navigate(notification.payload) is called after the optimistic read dispatch
If the deep link resolves to a valid route, the app navigates to that route without errors
If the target entity has been deleted, a snackbar with a user-friendly message is displayed and the notification remains marked as read
If the payload is malformed or the route is unknown, the fallback (snackbar + no navigation) is triggered and no crash occurs
Unread notifications display a left accent border using the design token color and a bold title using the design token font weight
Read notifications display no accent border and a normal-weight title
The unread count badge in NotificationTabBadge decrements immediately on tap (driven by BLoC state)
Re-tapping an already-read notification does not dispatch MarkNotificationRead again

Technical Requirements

frameworks
Flutter
BLoC
apis
NotificationDeepLinkHandler.navigate(payload)
BLoC MarkNotificationRead event
data models
Notification (id, payload, isRead)
NotificationPayload (type, entityId, route)
performance requirements
Optimistic UI update must be synchronous — zero perceptible delay between tap and visual read state change
Deep link resolution must not block the UI thread; use async/await in the handler, not in the widget build method
security requirements
Deep link payload must be validated before navigation to prevent open redirect attacks
Entity ID in the payload must be checked for authorization before navigating to the target screen
ui components
GestureDetector or InkWell wrapping NotificationListItem
Left accent border using Container with BoxDecoration (design token color)
Bold title using design token typography class for unread state

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

In NotificationListItem, use an onTap callback rather than embedding navigation logic directly in the widget — keep the widget dumb. In NotificationCentreScreen, provide the onTap as: `onTap: () { if (!notification.isRead) { context.read().add(MarkNotificationRead(notification.id)); } handler.navigate(notification.payload); }`. For the visual treatment, use a conditional BoxDecoration on the item container: if `!notification.isRead`, apply a 3dp left border using the design token accent color (e.g., `AppColors.accentUnread`). For the title weight, use `isRead ?

AppTextStyles.bodyRegular : AppTextStyles.bodyBold` from the design token system. Do not store `isRead` in local widget state — derive it entirely from the BLoC state to avoid stale UI.

Testing Requirements

Write flutter_test widget tests covering: (1) tapping an unread item dispatches MarkNotificationRead before navigate is called (verify call order with a mock handler), (2) item visual state changes to read immediately after tap, (3) deep link handler called with the correct payload from the tapped notification, (4) deleted entity fallback renders snackbar without crashing, (5) malformed payload triggers fallback gracefully, (6) re-tapping a read item does not dispatch MarkNotificationRead. Use mockito or mocktail to mock NotificationDeepLinkHandler. Verify BLoC state transitions with bloc_test.

Component
Notification Centre Screen
ui medium
Epic Risks (3)
medium impact medium prob integration

If a referenced entity (contact, certification, activity) has been deleted or its RLS policy now excludes the current user, the deep link handler may navigate to a screen that renders in an error state or throws an unhandled exception.

Mitigation & Contingency

Mitigation: The deep link handler must perform a lightweight existence check (HEAD request or minimal SELECT) before pushing the route. Define a contract with each destination screen for how to handle a not-found entity ID passed as a route parameter.

Contingency: If the existence check itself fails (network error), navigate to the destination screen anyway and let it handle the error gracefully with its own error state; do not block navigation for network timeouts.

medium impact low prob technical

If the tab badge widget triggers a full rebuild of the bottom navigation bar on every unread count change, it will cause visible jank on devices with many active Realtime events (e.g., org admins receiving org-wide alerts).

Mitigation & Contingency

Mitigation: Scope the badge widget to a dedicated BlocSelector that rebuilds only when the unread count value changes, not on any BLoC state emission. Use RepaintBoundary to isolate the badge from the rest of the nav bar.

Contingency: If performance issues persist, debounce badge updates to a maximum of one rebuild per 500ms and display the last known count during the debounce window.

high impact medium prob scope

Complex swipe-to-mark-read gestures and dynamic list updates may conflict with VoiceOver/TalkBack navigation patterns, particularly for Blindeforbundet users who rely exclusively on screen readers.

Mitigation & Contingency

Mitigation: Provide a dedicated accessibility action (Semantics.onTap / CustomSemanticsAction) for mark-as-read on each list item so screen reader users do not need the swipe gesture. Test with VoiceOver on iOS and TalkBack on Android before each release.

Contingency: If the swipe gesture proves incompatible with assistive technologies, disable it when a screen reader is detected (via ScreenReaderDetectionService) and rely solely on the tap-to-read and accessible action pathways.