high priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

ApprovalStatusNotificationService exposes: notifyClaimStatusChange(String claimId, ClaimStatus newStatus, String peerMentorId, {String? justification}) → Future<void>
Method dispatches both a push notification (via FCM dispatcher) and an in-app notification (via in-app notification repository) in parallel — one failure must not prevent the other from being sent
Notification content is determined by a localized template map keyed by ClaimStatus: approved → 'Your claim has been approved', rejected → 'Your claim was rejected: {justification}', more_info_requested → 'Your coordinator needs more information about your claim'
Templates support Norwegian (nb) and English (en) locales — locale is resolved from the peer mentor's stored language preference in Supabase, defaulting to Norwegian
justification is injected into the rejected template only when present; if absent on a rejection, the template uses a generic fallback message
Push notification includes a data payload with: claim_id, new_status, deep_link_path (e.g., /claims/{claimId}) for direct navigation on tap
In-app notification is persisted to the notifications table with: recipient_id, title, body, claim_id, status, read (false), created_at
If the peer mentor has no registered FCM token, the push notification is skipped silently (not an error) but the in-app notification is still created
All notification dispatch attempts are logged (success or failure) to an audit table for support traceability
Service is covered by tests for each ClaimStatus transition and both locale paths

Technical Requirements

frameworks
Flutter
Dart
supabase_flutter
firebase_messaging (FCM)
flutter_local_notifications (for in-app)
apis
Firebase Cloud Messaging (FCM) HTTP v1 API
Supabase PostgREST (notifications table, peer mentor profile)
Supabase Edge Function (optional: FCM dispatch from server side)
data models
NotificationPayload
InAppNotification
PeerMentorProfile
ClaimStatus
performance requirements
Total dispatch time (push + in-app) must complete within 3 seconds — both are fired in parallel via Future.wait
Locale resolution must be cached alongside the peer mentor profile — no extra DB round-trip per notification
security requirements
FCM server key must never be stored in the Flutter client — FCM dispatch must go through a Supabase Edge Function or server-side function to protect the key
Notification body must not include full claim amount or PII beyond what is necessary for the peer mentor to act
In-app notifications table must have RLS: peer mentors can only SELECT their own notifications (recipient_id = auth.uid())
Deep link paths must be validated against an allowed list to prevent open redirect via notification tap

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Keep notification template strings in a dedicated NotificationTemplates class with a static method resolveTemplate(ClaimStatus, Locale, {String? justification}) → NotificationContent to make the template logic independently testable. For FCM dispatch, implement a thin FcmDispatcher interface with a SupabaseEdgeFunctionFcmDispatcher concrete implementation — this keeps the service testable without real FCM calls. Use Future.wait([sendPush(), createInApp()]) wrapped in a try/catch that captures partial failures independently, logs each, and rethrows only if both fail.

The workshop notes emphasise that peer mentors (likepersoner) have diverse digital skill levels — keep notification copy simple, action-oriented, and jargon-free in both languages.

Testing Requirements

Unit tests: mock FCM dispatcher and in-app notification repository. Verify that for each ClaimStatus, the correct template key is selected and the correct locale is applied. Test that FCM failure does not prevent in-app notification creation (and vice versa). Test missing FCM token path (push skipped, in-app created).

Test justification injection into the rejection template with and without justification text. Test Norwegian and English locale outputs for each status. Integration test: write a notification to the Supabase test notifications table and verify the row has the correct recipient_id, claim_id, and read=false. Use flutter_test.

Target 90%+ line coverage on template resolution and dispatch logic.

Epic Risks (3)
medium impact medium prob technical

Maintaining multi-select state across paginated list pages is architecturally complex in Flutter with Riverpod/BLoC. If the selection state is stored in the widget tree rather than the state layer, page transitions and list redraws can silently clear selections, causing coordinators to lose their multi-select and re-enter it.

Mitigation & Contingency

Mitigation: Store the selected claim ID set in a dedicated Riverpod StateNotifier outside the paginated list widget tree. The paginated list reads selection state from this provider and does not own it. Selection persists independently of list scroll position or page loads.

Contingency: If cross-page selection proves prohibitively complex, limit bulk selection to the currently visible page (add a clear warning in the UI) and prioritise single-page bulk approval for the initial release.

medium impact medium prob integration

If a coordinator has the queue open while another coordinator approves claims from the same queue (possible in large organisations with shared chapter coverage), the Realtime update may arrive out of order or be missed during a reconnect, leaving the first coordinator's view stale and allowing them to attempt to approve an already-actioned claim.

Mitigation & Contingency

Mitigation: The ApprovalWorkflowService's optimistic locking (from the foundation epic) will catch the concurrent edit at the database level. The CoordinatorReviewQueueScreen should handle the resulting ConcurrencyException by removing the claim from the local list and showing a brief snackbar: 'This claim was already actioned by another coordinator.'

Contingency: Add a queue staleness indicator (a subtle 'last updated X seconds ago' label) and a manual refresh button as a fallback for coordinators who notice inconsistencies.

low impact high prob dependency

The end-to-end test requirement that a peer mentor receives a push notification within 30 seconds of coordinator approval depends on FCM delivery latency, which is outside the application's control and can vary significantly in CI/CD environments.

Mitigation & Contingency

Mitigation: Structure end-to-end tests to verify notification intent (correct FCM payload dispatched, correct Realtime event emitted) rather than actual device delivery timing. Use test doubles for FCM delivery in automated tests and reserve real-device delivery tests for manual pre-release validation.

Contingency: If notification timing requirements must be validated in automation, instrument the ApprovalNotificationService with a test hook that records dispatch timestamps and assert against those rather than actual FCM callbacks.