high priority low complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

Android channel 'activity_reminders' is created with NotificationDetails(importance: Importance.high, channelDescription readable by TalkBack)
Android channel 'certification_alerts' is created with Importance.max and a distinct alert sound asset
Android channel 'pause_status' is created with Importance.defaultImportance and an accessible description
Android channel 'scenario_prompts' is created with Importance.defaultImportance and an accessible description
Android channel 'system_alerts' is created with Importance.high and default sound
All five Android channel names and descriptions pass TalkBack screen-reader readout without truncation or symbol artefacts
iOS UNNotificationCategory is registered for each channel equivalent with a 'mark-as-read' UNNotificationAction (identifier: 'MARK_READ', title localised)
Channel setup is idempotent: calling the init function twice does not duplicate channels or throw
WCAG 2.2 AA: all channel description strings use plain language, no jargon, sentence case, no emoji
Unit test confirms all five AndroidNotificationChannel objects are constructed with the correct importance values
Manual QA on Android 8+ confirms channels appear in Settings → App notifications with correct labels
Manual QA on iOS 16+ confirms notification category actions appear when long-pressing a notification

Technical Requirements

frameworks
Flutter
flutter_local_notifications ^17+
apis
Android NotificationChannel API (API 26+)
iOS UNUserNotificationCenter
UNNotificationAction
UNNotificationCategory
data models
NotificationChannel (local model: id, name, description, importance, sound)
performance requirements
Channel initialisation must complete within 200 ms on app cold start
No blocking I/O on main isolate during channel setup
security requirements
No PII stored in channel names or descriptions
Sound assets must be bundled — no remote asset loading

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Create a NotificationChannelConfig class (lib/notifications/channel_config.dart) with static const definitions for each channel ID and metadata. Call FlutterLocalNotificationsPlugin().initialize() inside a dedicated NotificationSetupService.init() method invoked from main() before runApp(). For iOS, pass the DarwinInitializationSettings with notificationCategories list. Use const strings for all channel IDs to prevent typo-driven mismatches at call sites.

Android channel descriptions must be set at first registration — they cannot be updated after creation without deleting and recreating the channel, so get them right from the start. Avoid using emoji or special Unicode characters in descriptions as TalkBack may read them as 'unknown character'. Bundle a short alert sound file (e.g. alert.aiff / alert.mp3) in android/app/src/main/res/raw/ for the certification_alerts channel.

Testing Requirements

Unit tests (flutter_test): verify AndroidNotificationChannel constructor arguments for all five channels; verify iOS category/action identifiers are correct strings. Widget test: confirm FlutterLocalNotificationsPlugin.initialize() is called exactly once during app startup. Manual QA checklist: open device notification settings and confirm channel labels are human-readable; activate TalkBack/VoiceOver and verify channel descriptions are announced correctly; trigger a test notification on each channel and verify sound/importance behaviour matches spec.

Component
Notification Accessibility Configuration
infrastructure low
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.