high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Pre-permission rationale dialog is shown before the OS prompt on first launch when permission status is notDetermined
Dialog body text lists at least three specific reasons: activity reminders, certification expiry alerts, and important HLF updates — in plain Norwegian/English matching app locale
Dialog has exactly two actions: primary 'Allow' button and secondary 'Not Now' text button
Tapping 'Allow' dismisses the dialog and triggers the OS permission prompt (task-009/010)
Tapping 'Not Now' dismisses the dialog without triggering the OS prompt; permission result is stored as notDetermined
For permanently denied state, a separate settings-redirect dialog is shown with 'Open Settings' and 'Cancel' actions
Tapping 'Open Settings' calls openAppSettings() (permission_handler) and dismisses the dialog
All dialog text nodes are wrapped in Semantics widgets with meaningful labels for VoiceOver/TalkBack
Dialog uses design token colours and typography — no hardcoded hex values or font sizes
WCAG 2.2 AA: minimum contrast ratio 4.5:1 for all text; minimum touch target 44×44 dp for all buttons
Widget test confirms correct widget tree, button callbacks, and Semantics labels

Technical Requirements

frameworks
Flutter
permission_handler ^11+ (for openAppSettings)
Riverpod (to read locale/design tokens)
apis
openAppSettings() from permission_handler
Flutter Semantics API
Navigator for dialog routing
data models
PermissionRationaleConfig (title, body, allowLabel, notNowLabel — localised strings)
performance requirements
Dialog must be renderable within one frame (~16 ms) — no async work on open
No network calls triggered by dialog display
security requirements
Dialog must not display any user-identifying information
No analytics events triggered from inside the dialog widget itself — fire events at the caller layer
ui components
AppDialog (reusable dialog wrapper using design tokens)
AppButton (primary action)
AppTextButton (secondary action)
Semantics (wrapping title, body, and each button)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement as two stateless widget dialogs: PermissionRationaleDialog and PermissionDeniedSettingsDialog, both in lib/notifications/widgets/. Do not use showDialog directly inside the permission manager — instead expose callbacks (onAllow, onNotNow) so the calling widget controls navigation, keeping the dialogs purely presentational and easily testable. Use the app's existing AppButton and AppTextField design token primitives rather than raw ElevatedButton/TextButton to ensure consistent styling. For Semantics, wrap the entire dialog content in a Semantics(container: true) and add individual Semantics(label: '...', button: true) to each action button.

The cognitive accessibility requirement from the workshop (plain language, no jargon) maps directly to dialog copy: write at a Grade 6 reading level, use short sentences, and avoid technical terms like 'push notification' — say 'app messages' or 'reminders' instead. Confirm copy with HLF stakeholder before finalising.

Testing Requirements

Widget tests (flutter_test): render PermissionRationaleDialog and assert title, body, 'Allow' button, and 'Not Now' button are present in the widget tree; tap 'Allow' and verify onAllow callback is called; tap 'Not Now' and verify onNotNow callback is called; verify Semantics labels exist on all interactive elements. Widget test for settings-redirect dialog: assert 'Open Settings' and 'Cancel' buttons; tap 'Open Settings' and verify openAppSettings() mock is called. Accessibility test: use flutter_test's SemanticsHandle to assert all interactive nodes have non-empty labels. Visual QA: compare dialog against design spec on both iOS and Android, verify contrast ratios with a contrast checker tool.

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