high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Screen renders four toggle rows: 'Activity Reminders', 'Certification Expiry', 'Pause Status', 'Scenario Prompts' — each with a title, one-line description, and a Switch widget
When OS push permission is denied, a dismissible banner appears at the top of the screen with the text 'Notifications are disabled' and a 'Open Settings' button that invokes openAppSettings() from the permission handler package
When OS push permission is granted, the banner is not shown
Toggling any switch immediately calls NotificationPreferencesRepository.updateCategoryPreference() and optimistically updates the UI without requiring a save button
If the Supabase write fails, the toggle reverts to its previous state and a SnackBar error message is shown
On screen load, toggle states are initialised from the current preferences returned by NotificationPreferencesRepository
All toggle labels and descriptions meet WCAG 2.2 AA contrast ratio (≥ 4.5:1 for normal text, ≥ 3:1 for large text) using design tokens
Each toggle row has a Semantics widget with a meaningful label read by VoiceOver/TalkBack (e.g. 'Activity Reminders, on')
Screen title in AppBar is 'Notification Settings' and uses the standard page header design token
Screen is accessible via Settings screen navigation without any back-navigation regressions

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase REST API via NotificationPreferencesRepository
data models
NotificationPreferences
NotificationCategory
performance requirements
Toggle state persists within 500ms of user interaction (optimistic update)
Screen initial load completes within 300ms from cached preferences
security requirements
Preferences are scoped to the authenticated user's Supabase row — enforce RLS policy on notification_preferences table
No notification category labels or descriptions may contain PII
ui components
Switch (Flutter material)
ListTile with trailing Switch for each category row
MaterialBanner or custom banner widget for OS permission status
SnackBar for error feedback
AppBar with back navigation

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use a BLoC/Cubit that holds a Map as state alongside a PermissionStatus for the OS banner. Load preferences in the Cubit constructor via repository injection. Each SwitchListTile's onChanged should dispatch a ToggleCategoryEvent; the Cubit performs the async Supabase write and emits a rollback state on failure. For the OS permission banner, call Permission.notification.status in the Cubit's init method and re-check on screen resume (use WidgetsBindingObserver).

Use AppOpenSettings or url_launcher with app-settings: URI for the 'Open Settings' button. All colors, text styles, and spacing must reference design tokens — avoid hardcoded values. Keep the repository interface thin: one method to fetch all preferences, one method to update a single category.

Testing Requirements

Unit tests: test NotificationSettingsCubit/Bloc state transitions for toggle-on, toggle-off, load-success, load-failure, and save-failure states using flutter_test and bloc_test. Widget tests: render NotificationSettingsScreen with mocked repository returning granted/denied permission and verify banner visibility, toggle initial values, and Semantics labels. Integration test: tap a toggle, verify repository.updateCategoryPreference() is called with correct arguments, simulate a Supabase failure and verify toggle revert + SnackBar. Accessibility test: run flutter_test semantics inspector to confirm all interactive elements have meaningful labels.

Target 90% line coverage on cubit/bloc and widget.

Component
Notification Settings Screen
ui low
Epic Risks (2)
medium impact medium prob technical

The notification badge widget depends on a persistent Supabase Realtime websocket subscription for live unread count updates. On mobile, network transitions (WiFi to cellular, background app state) can silently drop the websocket, resulting in a stale badge count that does not update until the next app foreground — reducing trust in the notification system.

Mitigation & Contingency

Mitigation: Implement connection lifecycle management in the badge widget's BLoC that re-subscribes on app foreground and on network reconnection events. Add a fallback polling query (every 60 seconds when app is foregrounded) to reconcile the badge count if the Realtime subscription is interrupted.

Contingency: If Realtime reliability proves insufficient in production, replace the live subscription with a polling approach using a configurable interval, accepting slightly delayed badge updates in exchange for reliability.

medium impact medium prob technical

The notification list item widget requires merged semantics combining title, body, timestamp, read state, and role-context icon into a single VoiceOver/TalkBack announcement. Getting the merged semantics structure right for both iOS (VoiceOver) and Android (TalkBack) simultaneously is non-trivial and common to break silently when widgets are refactored.

Mitigation & Contingency

Mitigation: Use the project's existing semantics-wrapper-widget pattern with explicit Semantics widgets and excludeSemantics on decorative children. Write accessibility widget tests using Flutter's SemanticsController to assert the exact announcement string. Test on physical devices with VoiceOver and TalkBack enabled before release.

Contingency: If merged semantics cannot be achieved cleanly on both platforms, implement platform-specific semantic trees using defaultTargetPlatform branching, ensuring each platform receives an optimal announcement even if the implementation differs.