high priority medium complexity frontend pending fullstack developer Tier 4

Acceptance Criteria

Foreground notifications received via FirebaseMessaging.onMessage are persisted to NotificationRepository
Background notifications received via FirebaseMessaging.onBackgroundMessage are persisted to NotificationRepository
Cold-start notifications (via getInitialMessage) are persisted to NotificationRepository without duplication
Each persisted record includes: notification_id (FCM message_id), category, title, body, payload (JSON string), received_at (UTC timestamp), is_read (default false)
Idempotency check uses notification_id as the unique key — inserting a duplicate notification_id is silently ignored (upsert with conflict on notification_id)
Persistence does not block the UI thread — all writes are async and do not await on the main isolate for background handler
Notification history is retrievable via NotificationRepository.getHistory() returning a list ordered by received_at descending
Persisted notifications survive app restarts and are still visible in the Notification Center after cold start
Failed persistence writes are logged but do not crash the app or disrupt the notification display flow

Technical Requirements

frameworks
Flutter
firebase_messaging
Supabase Dart client
BLoC
apis
Supabase REST API (notifications table upsert)
FirebaseMessaging.onMessage stream
FirebaseMessaging.onBackgroundMessage top-level handler
data models
NotificationRecord
NotificationPayload
NotificationRepository
performance requirements
Background handler must complete Supabase write within 30 seconds (FCM background handler timeout)
Supabase upsert on notification_id must use ON CONFLICT DO NOTHING for O(1) idempotency
NotificationRepository.getHistory() query must be paginated (limit 50 per page) to prevent loading unbounded history
security requirements
Notification body and payload stored in Supabase must be row-level security protected so users can only read their own notification records
Payload JSON stored in the database must be sanitized — strip any fields not in the allowed schema before persistence

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

The FirebaseMessaging.onBackgroundMessage handler must be a top-level Dart function (not a class method) and must call Supabase.initialize() before performing any database operations, since it runs in a separate isolate without app context. Use an upsert with onConflict: 'notification_id' and ignoreDuplicates: true on the Supabase client to handle idempotency at the database level rather than with a pre-check query. Define the NotificationRecord model with a fromRemoteMessage(RemoteMessage) factory constructor to standardize conversion across all three app states. Store payload as a JSON string (jsonEncode(message.data)) rather than a structured column so the schema stays flexible as payload types evolve.

Testing Requirements

Unit tests (flutter_test) for NotificationRepository covering: insert of a new notification record succeeds; duplicate notification_id insert is silently ignored; getHistory() returns records in descending received_at order; failed Supabase write is caught and logged without throwing. Integration tests for PushNotificationService covering foreground, background, and cold-start persistence paths. Test that the background message handler (top-level Dart function) correctly initializes Supabase before writing. Mock Supabase client in unit tests.

Use a real Supabase test project for integration tests.

Component
Push Notification Service
infrastructure medium
Epic Risks (4)
high impact high prob technical

Flutter's background message handler for FCM must run in a separate Dart isolate. Incorrect dependency initialization in the isolate (e.g., attempting to access Riverpod providers or Supabase before initialization) will cause silent crashes on Android when the app is terminated, resulting in missed notifications that are invisible in crash reporting.

Mitigation & Contingency

Mitigation: Use a minimal top-level background handler function annotated with @pragma('vm:entry-point') that only stores the raw RemoteMessage payload to a platform channel or shared preferences. Process the payload in the main isolate on next app launch. Write an explicit test for terminated-state message handling on Android.

Contingency: If isolate crashes are observed, implement a native Android FirebaseMessagingService subclass that handles background messages without Flutter isolate complexity, falling back to a database-insert-only approach for terminated-state notifications.

medium impact medium prob technical

Supabase Edge Functions can experience cold-start latency of 1–3 seconds after periods of inactivity. For high-frequency events like assignment creation, cumulative cold starts could cause dispatch delays exceeding the 30-second SLA, reducing the perceived reliability of the notification system.

Mitigation & Contingency

Mitigation: Configure the Edge Function with a keep-warm ping mechanism or use Supabase database webhooks that invoke the function directly on row insert to minimize cold-start frequency. Batch preference lookups within the function to reduce per-invocation Supabase round-trips.

Contingency: If latency SLA is consistently breached, move to a polling or Realtime-subscription architecture within the Edge Function, or pre-compute dispatch targets at preference-save time to eliminate per-dispatch preference queries.

high impact low prob security

If the deep link handler does not perform server-side role validation before rendering the target screen, a peer mentor who receives a mis-configured notification payload containing a coordinator-only route could access restricted data, violating the role-based access control invariants.

Mitigation & Contingency

Mitigation: The deep link handler must check the user's current role from the RoleStateManager before constructing the navigation route. Coordinator-only routes must be listed in a deny-list checked against the current role. The go_router route guard is a second line of defence.

Contingency: If a role bypass is discovered in testing, immediately add the affected route to the deep link handler deny-list and add a regression test. Audit all notification payload types for route targets that could expose cross-role data.

medium impact low prob dependency

FCM v1 HTTP API enforces per-project send quotas. For large organisations with many active peer mentors receiving simultaneous assignment notifications, batch dispatch events (e.g., bulk coordinator assignments) could approach quota limits and result in dropped notifications with 429 errors logged silently.

Mitigation & Contingency

Mitigation: Implement exponential backoff retry logic in the Edge Function for 429 responses. Design bulk assignment flows to dispatch notifications in batches with a configurable delay between batches. Monitor FCM console quotas during load testing.

Contingency: If quota limits are hit, implement a notification queue table in Supabase and a separate Edge Function that processes the queue with rate limiting, ensuring eventual delivery without exceeding FCM quotas.