high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Widget renders notification icon (category-specific or fallback), title (max 1 line, ellipsis), body preview (max 2 lines, ellipsis), and relative timestamp ('2m ago', 'Yesterday') using design token typography
Unread notifications display a filled circular dot (design token accent colour) on the leading edge; read notifications display no dot
Swipe-to-dismiss (Dismissible widget, DismissDirection.endToStart) triggers HapticFeedback.mediumImpact and calls the provided onDismiss callback
Tapping anywhere on the card calls onTap callback — touch target is minimum 48×48dp as required by WCAG 2.2 AA
Semantic label combines title and body for screen readers (VoiceOver/TalkBack) and includes 'unread' qualifier when applicable
All colours, spacing, and typography use design tokens only — no hardcoded values
Widget is stateless and accepts a NotificationRecord model plus onTap, onDismiss callbacks as constructor parameters
Golden tests confirm read and unread visual states render correctly
Widget renders correctly at system font scales 1.0× through 2.0× without overflow

Technical Requirements

frameworks
Flutter
design token system (colors, typography, spacing, radii, sizing)
apis
HapticFeedback.mediumImpact()
Semantics widget
Dismissible widget
data models
NotificationRecord (id, title, body, category, isRead, createdAt, deepLinkTarget)
performance requirements
Widget must be const-constructible where possible to enable Flutter widget tree optimisation
Relative timestamp formatting must be computed outside the build method (pass pre-formatted string or use a pure utility function)
security requirements
Title and body text must be rendered as plain text (SelectableText or Text), never as HTML, to prevent injection
Do not expose notification entity IDs in accessible semantic descriptions
ui components
Dismissible
ListTile or custom Row layout
CircleAvatar (icon)
Dot indicator (Container with BoxDecoration)
Semantics wrapper

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Build as a StatelessWidget taking NotificationRecord, VoidCallback onTap, and VoidCallback onDismiss. Use the project's existing design token constants for all visual values — do not introduce new colour literals. Wrap the entire card in a Semantics widget with label built from title + body + ('unread' if !isRead). For the relative timestamp, use a utility function formatRelativeTime(DateTime) that returns localised strings ('Just now', '5m ago', 'Yesterday', date string) — this keeps the widget stateless and testable.

Wrap in Dismissible with a background showing a delete icon. Call HapticFeedback.mediumImpact() in the onDismissed callback before calling the parent onDismiss. Ensure the widget integrates with the NotificationCenterScreen's ListView.builder without requiring a Key (the parent ListView provides keys).

Testing Requirements

Widget tests (flutter_test): render with isRead=true and isRead=false and assert dot presence/absence. Test onTap callback fires on card tap. Test onDismiss callback fires after swipe gesture simulation (tester.drag). Test title/body overflow clamping with very long strings.

Test semantic label includes 'unread' when isRead=false. Golden tests: capture read and unread states at 1.0× and 2.0× text scale for regression. Accessibility audit: verify minimum touch target size programmatically using SemanticsNodeFinder.

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.