high priority low complexity backend pending fullstack developer Tier 1

Acceptance Criteria

On first app launch, requestPermission() is called once and the result is stored in SharedPreferences under key 'notification_permission_requested'
requestPermission() calls FirebaseMessaging.instance.requestPermission() with alert: true, badge: true, sound: true on iOS; on Android 13+ it calls permission_handler's Permission.notification.request()
getCurrentStatus() returns a NotificationPermissionStatus enum value: granted, denied, or provisional
When status is denied, an in-app bottom sheet is displayed with a clear explanation and a button that opens app system settings via openAppSettings() from permission_handler
A Riverpod StateProvider<NotificationPermissionStatus> named notificationPermissionProvider is exported and reflects the current status reactively
The provider is refreshed whenever the app resumes from background (AppLifecycleState.resumed) to detect changes made in system settings
The settings screen reads notificationPermissionProvider and displays a toggle-style row showing current status with a chevron to open settings when denied
A badge widget in the notifications tab shows a visual indicator when status is denied, prompting the user to enable notifications
All permission checks comply with WCAG 2.2 AA: the in-app prompt has sufficient color contrast, a dismiss action, and a descriptive label for screen readers

Technical Requirements

frameworks
Flutter
firebase_messaging
permission_handler
Riverpod
shared_preferences
apis
FirebaseMessaging.instance.requestPermission()
FirebaseMessaging.instance.getNotificationSettings()
permission_handler openAppSettings()
Permission.notification.status
performance requirements
Permission status check must complete within 200ms — it runs on every app resume
No network calls — this is a purely local operation
security requirements
Do not request notification permissions during onboarding before the user understands the value — show the request only after the home screen is rendered
ui components
NotificationPermissionBottomSheet — explanatory text + 'Open Settings' button + dismiss
NotificationPermissionStatusRow — settings screen list tile showing current status
NotificationBadgeWidget — tab badge shown when permissions denied

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Keep NotificationPermissionManager as a simple class, not a BLoC — permission state is light enough for a Riverpod StateProvider. The reactive refresh on AppLifecycleState.resumed should be handled by a WidgetsBindingObserver mixin in a dedicated provider widget at the root of the app tree, calling ref.invalidate(notificationPermissionProvider) on resume. On Android, firebase_messaging's requestPermission() is a no-op below API 33 — use permission_handler as a unified cross-platform API instead. The in-app bottom sheet must be dismissible by tapping outside (isDismissible: true) and must not be shown more than once per app session to avoid nagging.

Store the 'prompted_this_session' flag in memory, not SharedPreferences.

Testing Requirements

Write widget tests using flutter_test and mocktail. Mock FirebaseMessaging and permission_handler. Test cases: (1) first launch — requestPermission called exactly once, (2) subsequent launches — requestPermission NOT called again, (3) status = granted — provider emits granted, no bottom sheet, (4) status = denied — bottom sheet shown on trigger, (5) user taps 'Open Settings' — openAppSettings() called, (6) app resumes — provider refreshes status. Test the bottom sheet widget for WCAG contrast and semantic labels.

Target 85%+ coverage on NotificationPermissionManager.

Component
Notification Permission Manager
service low
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.