high priority low complexity database pending backend specialist Tier 0

Acceptance Criteria

NotificationPreferencesRepository exposes getPreferences(userId), updatePreference(userId, category, enabled), and initializeDefaults(userId, orgId) methods
Preferences load from local cache (SharedPreferences or Hive) on first read so the settings screen renders instantly without waiting for Supabase
On successful Supabase fetch, local cache is updated and the UI reflects remote state
updatePreference() optimistically updates local cache before awaiting the Supabase write
If the Supabase write fails, local cache is rolled back to the previous value and an error is surfaced to the caller
All operations are scoped by orgId — a multi-org user's preferences for org A do not affect org B
Repository returns typed Result<T, Failure> — no raw exceptions leak to the UI layer
Offline mode: when Supabase is unreachable, cached preferences are returned and a pending sync queue entry is created for the update

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Dart SDK
apis
Supabase PostgreSQL REST API
data models
accessibility_preferences
performance requirements
Cache read must complete in <10ms (synchronous local storage)
Supabase fetch must not block the UI — always return cached value first, then update via stream
security requirements
RLS: users can only read/write preference rows where user_id = auth.uid()
Cache key must include userId to prevent cross-user data leakage on shared devices
orgId must be derived from the authenticated JWT, not passed as a raw client parameter

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement a two-layer architecture: LocalNotificationPreferencesSource (SharedPreferences/Hive) and RemoteNotificationPreferencesSource (Supabase). NotificationPreferencesRepository orchestrates them via a cache-first strategy. Use a simple Map keyed by category name for the cache structure — serialize to JSON string for SharedPreferences storage. For the pending sync queue, store failed updates in a local list and replay on the next successful Supabase connection.

Expose preferences as a Stream> from Riverpod StreamProvider so the settings UI reactively updates. Define NotificationCategory as a sealed class or enum in a shared constants file to ensure type safety across repository and UI.

Testing Requirements

Unit tests (flutter_test + Mockito): (1) getPreferences returns cached value when offline; (2) successful Supabase fetch updates cache; (3) updatePreference applies optimistic update and rolls back on error; (4) initializeDefaults is idempotent. Mock both SupabaseClient and local storage. Assert that Result types are correctly returned for success and failure paths. Target 80% branch coverage.

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.