high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

`NotificationListItem` is a `StatelessWidget` accepting a `Notification notification`, a `VoidCallback onTap`, and a `VoidCallback onDismiss` as required parameters
The widget renders a type-specific icon using design token color values — each of the five NotificationType values maps to a distinct icon and color token (no hardcoded hex values in the widget)
The title text is derived from the notification payload type: ReminderPayload shows activity_type + 'reminder', ExpiryPayload shows course_name + 'expiring', ScenarioPayload shows prompt_text truncated to 60 chars, PausePayload shows 'Mentor on pause', SystemPayload shows the `message` field
The subtitle displays a human-readable relative timestamp ('just now', 'X minutes ago', 'X hours ago', 'yesterday', date string for older) derived from `notification.createdAt`
Unread notifications render with bold title text and a visible accent dot (color from design token); read notifications render with normal weight and no dot
`onTap` is invoked when the user taps anywhere on the item; for ScenarioPayload and SystemPayload with a deep_link/action_url, the caller is responsible for navigation
The item wraps in a `Dismissible` widget: swipe-left triggers `onDismiss`; a red delete background with trash icon is shown during the swipe
The entire widget is wrapped in a `Semantics` widget with a `label` combining type, read state, and relative time (e.g., 'Unread reminder notification, 2 hours ago') and `button: true`
Text contrast ratio meets WCAG 2.2 AA (minimum 4.5:1 for normal text, 3:1 for large text) using design token colors
Widget renders correctly at text scale factors 1.0, 1.5, and 2.0 without overflow or clipping
Widget tests cover: unread state rendering, read state rendering, all five notification types render distinct icons, onTap fires on tap, onDismiss fires on swipe, semantics label is present and non-empty

Technical Requirements

frameworks
Flutter
flutter_test
intl (for relative time formatting)
data models
Notification
NotificationType
ReminderPayload
ExpiryPayload
ScenarioPayload
PausePayload
SystemPayload
performance requirements
Widget build must complete in a single frame (under 16ms) — no async operations in build()
Relative timestamp computation must be synchronous and cached if the list rebuilds frequently
security requirements
All payload-derived text displayed in the UI must be HTML-escaped to prevent injection if rendered in a WebView context in future
deep_link and action_url values must not be rendered as raw text — they are passed to the onTap callback only, never displayed to the user
ui components
Dismissible (Flutter built-in)
Semantics (Flutter built-in)
ListTile or custom Row/Column layout
Design token color and typography system
Type-specific icon set (Material or custom)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Keep the widget purely presentational — no BLoC or Riverpod access inside. All data comes via constructor parameters. For relative time formatting, implement a `_formatRelativeTime(DateTime createdAt)` private function using `DateTime.now().difference(createdAt)` — handle durations: < 1min → 'just now', < 60min → 'X minutes ago', < 24h → 'X hours ago', < 48h → 'yesterday', else → `DateFormat.yMMMd().format(createdAt)` from the `intl` package. For the icon/color mapping, define a `_NotificationTypeVisuals` value object or extension on `NotificationType` that returns `({IconData icon, Color color})` — this keeps the switch statement out of the build method and makes it testable.

Use `Dismissible(key: ValueKey(notification.id), ...)` to ensure stable keys in lists. For the `Semantics` label, build it as: `'${isRead ? "Read" : "Unread"} ${typeLabel} notification, ${relativeTime}'`. Design tokens should be accessed via the app's theme extension or a `DesignTokens.of(context)` pattern — never via hardcoded hex values.

Testing Requirements

Widget tests in `notification_list_item_test.dart` using `flutter_test` and `WidgetTester`. Required test cases: (1) renders unread indicator (accent dot + bold) for an unread notification, (2) renders without indicator for a read notification, (3) each of the five NotificationType values renders a non-null, type-distinct icon (use `find.byIcon` or `find.byWidgetPredicate`), (4) tapping the widget invokes `onTap` callback exactly once, (5) swiping left triggers `onDismiss`, (6) `Semantics` label contains the word 'notification' and a time reference, (7) widget renders without overflow at `textScaleFactor: 2.0`. Use `TestWidgetsFlutterBinding` and `pumpWidget` with a minimal `MaterialApp` wrapper. Golden test (screenshot comparison) is optional but recommended for read vs.

unread visual states. Target 100% widget-level statement coverage for this file.

Component
Notification List Item
ui low
Epic Risks (3)
high impact medium prob technical

Supabase Realtime channels on mobile networks can drop silently. If reconnection logic is flawed, users miss notifications without knowing it, undermining the audit-trail guarantee.

Mitigation & Contingency

Mitigation: Implement exponential-backoff reconnection with a maximum of 5 retries; expose a channel-status stream to the BLoC so it can trigger a full-fetch fallback when the channel reconnects after a gap.

Contingency: If Realtime reliability proves insufficient in production, fall back to polling the repository every 60 seconds as a background supplement to the Realtime channel.

high impact medium prob security

Coordinator and org-admin RLS expansions require joining user_roles and org_memberships tables. An incorrect policy could expose notifications to wrong users or block legitimate access entirely.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration tests for each role (peer mentor, coordinator, org admin) using separate Supabase test projects. Review policies with the security checklist before merging.

Contingency: If an RLS defect is discovered post-deployment, disable the expanded-scope policy and revert to user-scoped-only access while a corrected migration is prepared and tested.

medium impact medium prob integration

JSONB payload structure may vary across notification types created by different Edge Functions (reminder, expiry, scenario, pause). Missing or renamed fields will cause runtime parse failures.

Mitigation & Contingency

Mitigation: Define a canonical NotificationPayload union type in a shared schema document. Each Edge Function must validate its payload against this schema before inserting. Add fallback parsing with default values in the domain model.

Contingency: Wrap all payload parsing in try/catch and log malformed payloads to a monitoring channel; render a generic notification item rather than crashing when the payload cannot be parsed.