critical priority medium complexity integration pending integration specialist Tier 4

Acceptance Criteria

Granting notification permission in the OS dialog immediately triggers FCM token registration without requiring an app restart
registerIfPermissionGranted() is a no-op when permission status is denied or notDetermined — no spurious registration attempts
Both NotificationPermissionManager and FCMTokenManager are provided via the same Riverpod container and share the same Supabase auth session
FCM token is persisted to the device_token table in Supabase with correct platform enum (ios/android) and the authenticated user's ID
If FCM token registration fails after permission grant, the error is captured and the user sees a non-blocking retry option
On Android, the permission request follows the POST_NOTIFICATIONS rational dialog flow for API 33+
The integration does not create a circular Riverpod dependency between the two providers
Manual revocation of permission (OS settings) and re-grant triggers a fresh token registration on the next app foreground event

Technical Requirements

frameworks
Flutter
Riverpod
firebase_messaging
flutter_local_notifications
apis
Firebase Cloud Messaging (FCM) API v1
Supabase PostgreSQL REST API
data models
device_token
performance requirements
Token registration must complete within 3 seconds of permission grant on a standard mobile connection
Permission status check must not block the UI thread — use async/await
security requirements
FCM server key and service account must never be bundled in the mobile binary — all server-side dispatch via Supabase Edge Functions
Device token stored in Supabase must be scoped to authenticated user_id with RLS (users can only read/write their own tokens)
JWT from current Supabase session must be passed as auth context for the token upsert — never use service role key client-side
Stale tokens for the same device must be replaced (upsert on device identifier) to prevent duplicate push delivery

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Model the integration as a unidirectional callback: NotificationPermissionManager.onPermissionGranted is a VoidCallback injected at construction via Riverpod. FCMTokenManager exposes a method registerIfPermissionGranted() that (1) calls FirebaseMessaging.instance.getToken(), (2) upserts the token into Supabase device_tokens with platform + user_id, (3) sets up FirebaseMessaging.instance.onTokenRefresh listener to handle automatic rotation. Provide both managers as Riverpod Provider (not StateNotifier) since they are stateless service objects. Use ref.read() inside FCMTokenManager to access the current Supabase session — avoid storing the session in a field to prevent stale auth state.

On Android API 33+ check Build.VERSION.SDK_INT before calling requestPermission to avoid redundant dialogs.

Testing Requirements

Unit tests (flutter_test + Mockito): (1) permission granted callback invokes registerIfPermissionGranted(); (2) permission denied — registerIfPermissionGranted() is not called; (3) FCM token retrieval failure — error handled gracefully without crash; (4) Riverpod provider graph resolves without circular dependency. Widget test: simulate OS permission grant on mock PermissionManager and assert FCMTokenManager.register() was called exactly once. Integration test (real device/TestFlight): grant permission, verify token row appears in Supabase device_tokens table. Target 80% coverage on both managers.

Component
FCM Token Manager
infrastructure medium
Epic Risks (3)
high impact medium prob scope

iOS only allows one system permission prompt per app install. If the rationale dialog timing or content is wrong the user may permanently deny permissions during onboarding, permanently blocking push delivery for that device with no recovery path short of manual system settings navigation.

Mitigation & Contingency

Mitigation: Design and user-test the rationale dialog content and trigger point (after onboarding value-demonstration step, not at first launch). Implement the settings-deep-link fallback in NotificationPermissionManager so the permission state screen always offers a path to system settings if denied.

Contingency: If denial rates are high in TestFlight testing, revise the rationale copy and trigger timing before production release. Ensure the in-app notification centre provides full value without push so denied users are not blocked from the feature.

medium impact medium prob technical

FCM token rotation callbacks can fire at any time, including during app termination or network outage. If the token rotation is not persisted reliably the backend trigger service will dispatch to a stale token, resulting in silent notification failures that are hard to diagnose.

Mitigation & Contingency

Mitigation: Persist token rotation updates with a local queue that retries on next app foreground if network is unavailable. Use Supabase upsert by (user_id, device_id) to prevent duplicate token rows and ensure the latest token always wins.

Contingency: If token staleness is observed in production, add a token validity check on each app foreground and force a re-registration if the stored token does not match the FCM-reported current token.

high impact low prob security

Incorrect RLS policies on notification_preferences or fcm_tokens could expose one user's preferences or device tokens to another user, or could block the backend Edge Function service role from reading token lists needed for dispatch, silently dropping all notifications.

Mitigation & Contingency

Mitigation: Write explicit RLS policy tests using the Supabase test harness covering user-scoped read/write, service-role read for dispatch, and cross-user access denial. Review policies during code review with a security checklist.

Contingency: Maintain a rollback migration that reverts the RLS changes, and add an integration test in CI that asserts the service role can query all tokens and that a normal user JWT cannot access another user's token rows.