high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

`NotificationAccessibilityAnnouncer` is a singleton (factory constructor or static instance) with no publicly mutable state
`announceNewNotification(Notification n)` calls `SemanticsService.announce()` with a human-readable string derived from the notification type and payload (e.g., 'New reminder: Follow up with contact due tomorrow')
`announceReadStateChange(int unreadCount)` announces a message like 'You have 3 unread notifications' or 'All notifications read' when count is 0
`announceListLoaded(int total)` announces 'Notification list loaded, N notifications' (or 'No notifications' when total is 0)
All announcement strings are externalized as localization keys (via Flutter's `AppLocalizations` or equivalent) — no hardcoded English strings in the class body
Announcements use `TextDirection.ltr` and the `assertive` polite level (`assertiveness: Assertiveness.polite`) to avoid interrupting ongoing speech
A minimum debounce interval of 500ms is applied between successive announcements to prevent rapid-fire TalkBack/VoiceOver interruptions
The announcer does NOT announce when the app is in the background or when `SemanticsBinding.instance.accessibilityFeatures.accessibleNavigation` is false (i.e., no screen reader active)
Tested manually on iOS (VoiceOver) and Android (TalkBack) — announcements are heard at appropriate moments without cutting off user speech
Unit tests verify correct announcement string construction for all five `NotificationType` values in `announceNewNotification`
Unit tests verify the debounce logic suppresses a second call within the debounce window

Technical Requirements

frameworks
Flutter
flutter/semantics.dart (SemanticsService)
flutter_test
apis
SemanticsService.announce()
SemanticsBinding.instance.accessibilityFeatures
data models
Notification
NotificationType
performance requirements
Announcement construction must be synchronous and complete in under 0.1ms
No network calls or async DB operations within the announcer
security requirements
Announcement strings must not include PII — use contact IDs or role labels rather than names or health data
For PausePayload announcements, do not vocalize pause_reason if it may contain sensitive content; use a generic 'Mentor on pause' message
ui components
SemanticsService (Flutter framework semantics layer)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Flutter's `SemanticsService.announce()` is the correct cross-platform API — it maps to `UIAccessibility.post(notification: .announcement)` on iOS and `AccessibilityManager.announceForAccessibility()` on Android internally. Use `Assertiveness.polite` so the announcement queues after current speech rather than interrupting. Implement debouncing with a `Timer` that resets on each rapid call — store the timer as a private field on the singleton. For the accessibility guard (`accessibleNavigation` check), wrap calls in `if (SemanticsBinding.instance.accessibilityFeatures.accessibleNavigation)` so the overhead is zero for non-screen-reader users.

For announcement string construction, implement a private `_buildAnnouncementText(Notification n)` switch on `n.type` that delegates to payload-specific helpers. Keep all user-facing strings in the app's localization file. This class has no dependency on BLoC or Riverpod — it is a pure utility called by the BLoC/cubit layer after state transitions.

Testing Requirements

Unit tests in `notification_accessibility_announcer_test.dart` using `flutter_test`. Mock `SemanticsService` using a test double or verify calls via `tester.binding`. Required test cases: (1) `announceNewNotification` generates correct string for each NotificationType (5 cases), (2) `announceReadStateChange(0)` produces 'all read' message, `announceReadStateChange(3)` produces count message, (3) `announceListLoaded(0)` produces empty state message, (4) rapid successive calls within debounce window result in only one SemanticsService.announce invocation, (5) no announcement is made when `accessibleNavigation` is false. Manual accessibility QA on physical devices (VoiceOver on iOS, TalkBack on Android) must be performed and sign-off recorded in the PR description.

Component
Notification Accessibility Announcer
infrastructure low
Epic Risks (3)
high impact medium prob technical

Supabase Realtime channels on mobile networks can drop silently. If reconnection logic is flawed, users miss notifications without knowing it, undermining the audit-trail guarantee.

Mitigation & Contingency

Mitigation: Implement exponential-backoff reconnection with a maximum of 5 retries; expose a channel-status stream to the BLoC so it can trigger a full-fetch fallback when the channel reconnects after a gap.

Contingency: If Realtime reliability proves insufficient in production, fall back to polling the repository every 60 seconds as a background supplement to the Realtime channel.

high impact medium prob security

Coordinator and org-admin RLS expansions require joining user_roles and org_memberships tables. An incorrect policy could expose notifications to wrong users or block legitimate access entirely.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration tests for each role (peer mentor, coordinator, org admin) using separate Supabase test projects. Review policies with the security checklist before merging.

Contingency: If an RLS defect is discovered post-deployment, disable the expanded-scope policy and revert to user-scoped-only access while a corrected migration is prepared and tested.

medium impact medium prob integration

JSONB payload structure may vary across notification types created by different Edge Functions (reminder, expiry, scenario, pause). Missing or renamed fields will cause runtime parse failures.

Mitigation & Contingency

Mitigation: Define a canonical NotificationPayload union type in a shared schema document. Each Edge Function must validate its payload against this schema before inserting. Add fallback parsing with default values in the domain model.

Contingency: Wrap all payload parsing in try/catch and log malformed payloads to a monitoring channel; render a generic notification item rather than crashing when the payload cannot be parsed.