high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Screen renders a scrollable list of NotificationListItemWidget cards for all notifications belonging to the authenticated user, ordered by created_at descending
Unread notifications display bold title text and a filled accent dot (design token color) on the leading edge; read notifications display regular weight title and no dot
Tapping a notification marks it as read in Supabase, removes the bold/dot styling, and navigates to the relevant deep-link destination
Pull-to-refresh (RefreshIndicator) refetches the first page from Supabase and resets pagination state
Infinite scroll: when the user scrolls within 200px of the bottom, the next page (20 items) is fetched and appended; a loading spinner is shown at the bottom during fetch
If there are no notifications, an empty state illustration and the text 'No notifications yet' are displayed instead of the list
AppBar includes a 'Mark all as read' action (icon button); tapping it calls NotificationRepository.markAllAsRead(), updates all visible items to read styling, and hides the NotificationBadgeWidget count
Swipe-to-dismiss on a list item calls NotificationRepository.deleteNotification(id) and removes the item from the list with a slide-out animation; an undo SnackBar is shown for 4 seconds
If undo is tapped within 4 seconds, the deletion is cancelled and the item is restored
All list items have Semantics with a label including the notification title, relative time, and read/unread state (e.g. 'Activity reminder, 2 hours ago, unread')
Screen passes WCAG 2.2 AA contrast checks for all text and icon elements using design tokens

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase REST API — paginated query on notifications table (range-based pagination)
Supabase REST API — PATCH for mark-as-read and mark-all-read
Supabase REST API — DELETE for dismiss
data models
Notification
NotificationStatus (read/unread)
PaginationCursor
performance requirements
First page (20 items) renders within 400ms on a standard network
Subsequent pages append without full list re-render (use ListView.builder with a growing items list)
Swipe-to-dismiss animation completes within 300ms
security requirements
All Supabase queries must enforce RLS — users can only read/delete their own notifications
Deep-link navigation from notification tap must validate the target resource belongs to the user before navigating
ui components
ListView.builder with NotificationListItemWidget
RefreshIndicator
Dismissible widget for swipe-to-dismiss
CircularProgressIndicator footer for pagination loading
Empty state widget (illustration + text)
IconButton in AppBar for mark-all-read
SnackBar with undo action

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Model pagination as offset+limit (page * 20). Cubit holds a List items, bool isLoading, bool hasMore, bool isLoadingMore. Use a ScrollController attached to ListView.builder; listen to scroll position in the screen widget and dispatch LoadMoreEvent when position > maxScrollExtent - 200. For swipe-to-dismiss with undo: remove from state immediately (optimistic), store the dismissed item in a pendingDelete field, schedule the Supabase DELETE after a 4-second delay; cancel the timer if undo fires.

For mark-all-read, update the local list in state (map all items to read: true) before the Supabase call completes (optimistic). Use Dismissible's confirmDismiss to show the SnackBar and handle undo logic cleanly. All visual styles (bold weight, accent dot size/color, card padding) must use design tokens — define named token constants rather than inline values.

Testing Requirements

Unit tests: test NotificationCenterCubit state machine — initial load success, load failure, load-more (pagination), mark-as-read, mark-all-read, dismiss, undo-dismiss. Use bloc_test with mocked NotificationRepository. Widget tests: render screen with a list of 5 mock notifications (mix of read/unread), verify bold styling on unread items, verify empty state when list is empty, verify AppBar 'Mark all as read' button triggers correct cubit event. Test swipe-to-dismiss renders SnackBar.

Test infinite scroll by pumping a scroll event near the bottom and verifying load-more event fires. Integration test: real paginated Supabase query with 25 seed records — verify two pages load. Accessibility test: verify Semantics labels on 3 notification items. Target 85% coverage on cubit and 75% on widget.

Component
Notification Center Screen
ui medium
Dependencies (4)
Build the NotificationRepository data layer that fetches paginated notification history from Supabase, supports mark-as-read and mark-all-read mutations, and provides a Supabase Realtime stream of the unread count integer. Implement cursor-based pagination for efficient loading of large notification histories. epic-push-notification-delivery-ui-task-002 Implement the NotificationListItemWidget as a stateless Flutter widget displaying notification icon, title, body preview, relative timestamp, and a visual read/unread indicator dot. Support swipe-to-dismiss gesture with haptic feedback. Apply accessibility config semantic labels and ensure 48dp minimum touch target. Wire onTap to deep-link navigation callback. epic-push-notification-delivery-ui-task-007 Implement the NotificationBadgeWidget as a small overlay badge on the bottom navigation Notifications tab that subscribes to the Supabase Realtime unread count stream from NotificationRepository. Animate badge appearance and disappearance, cap displayed count at 99+, and update immediately on mark-all-read. Ensure VoiceOver announces unread count as semantic label on the tab item. epic-push-notification-delivery-ui-task-011 Build the PushNotificationService that integrates FCMTokenManager and NotificationDeepLinkHandler to handle incoming FCM messages in foreground, background, and terminated states. Persist received notifications via NotificationRepository, show local in-app banners for foreground messages, and route deep links on notification tap. Handle all Firebase message lifecycle callbacks. epic-push-notification-delivery-ui-task-008
Epic Risks (2)
medium impact medium prob technical

The notification badge widget depends on a persistent Supabase Realtime websocket subscription for live unread count updates. On mobile, network transitions (WiFi to cellular, background app state) can silently drop the websocket, resulting in a stale badge count that does not update until the next app foreground — reducing trust in the notification system.

Mitigation & Contingency

Mitigation: Implement connection lifecycle management in the badge widget's BLoC that re-subscribes on app foreground and on network reconnection events. Add a fallback polling query (every 60 seconds when app is foregrounded) to reconcile the badge count if the Realtime subscription is interrupted.

Contingency: If Realtime reliability proves insufficient in production, replace the live subscription with a polling approach using a configurable interval, accepting slightly delayed badge updates in exchange for reliability.

medium impact medium prob technical

The notification list item widget requires merged semantics combining title, body, timestamp, read state, and role-context icon into a single VoiceOver/TalkBack announcement. Getting the merged semantics structure right for both iOS (VoiceOver) and Android (TalkBack) simultaneously is non-trivial and common to break silently when widgets are refactored.

Mitigation & Contingency

Mitigation: Use the project's existing semantics-wrapper-widget pattern with explicit Semantics widgets and excludeSemantics on decorative children. Write accessibility widget tests using Flutter's SemanticsController to assert the exact announcement string. Test on physical devices with VoiceOver and TalkBack enabled before release.

Contingency: If merged semantics cannot be achieved cleanly on both platforms, implement platform-specific semantic trees using defaultTargetPlatform branching, ensuring each platform receives an optimal announcement even if the implementation differs.