Implement Read State Service with optimistic updates
epic-in-app-notification-centre-services-task-004 — Implement the Notification Read State Service class that wraps notification repository mutation calls. On mark-read or mark-all-read invocation, immediately emit an optimistic state update to the caller before the async repository call completes. Record the pre-mutation state snapshot for rollback purposes. Set read_at timestamp using UTC server time from the repository response, falling back to local DateTime.now().toUtc() if the server timestamp is absent.
Acceptance Criteria
Technical Requirements
Implementation Notes
Implement `NotificationReadStateService` with a `StreamController
Keep `ReadStateUpdate` as a simple immutable value class. Expose `Stream
Testing Requirements
Unit tests (flutter_test) using a mock NotificationRepository. Test: (1) markAsRead emits optimistic update before repository future completes (use Completer to control timing), (2) read_at uses server timestamp when present, (3) read_at falls back to local UTC when server timestamp is null, (4) markAllRead emits optimistic update for all unread items, (5) snapshot is captured before mutation. Use fake_async or manual Completer patterns to verify emit ordering. Verify the in-flight set correctly tracks pending IDs.
Integration test with supabase_flutter test utilities verifying the PATCH call is issued with correct payload.
A Realtime INSERT event arriving during an in-flight mark-all-read operation can cause the new notification to be incorrectly marked read in the optimistic state update, silently hiding it from the user.
Mitigation & Contingency
Mitigation: Process Realtime events sequentially in the BLoC event queue using bloc_concurrency's sequential transformer. The mark-all-read event should only affect notifications whose IDs were fetched before the operation started.
Contingency: Add a reconciliation step after mark-all-read that re-fetches the unread count from the repository and corrects the BLoC state if it diverges from the server value.
A coordinator assigned to many peer mentors may trigger a query returning hundreds or thousands of notifications. Without pagination and query optimisation, the initial load will be slow and memory-heavy.
Mitigation & Contingency
Mitigation: Enforce server-side pagination (50 items per page) in the Role-Aware Filter's query predicates. Add a composite index on (org_id, user_id, created_at DESC) and profile query plans before shipping.
Contingency: If query performance is insufficient for large coordinator scopes, introduce a server-side RPC function that pre-aggregates visible notification IDs and returns only the first page, deferring full scope resolution to lazy-load.
If the read-state optimistic update rolls back frequently due to intermittent connectivity, users will observe notifications toggling between read and unread, creating confusion and distrust of the feature.
Mitigation & Contingency
Mitigation: Queue failed mutations in a local retry store and re-attempt on next connectivity event using a connectivity-aware retry service. Show a non-intrusive banner if offline rather than applying optimistic updates.
Contingency: Disable optimistic updates for mark-as-read in low-connectivity scenarios detected by the connectivity provider, instead showing a loading indicator until server confirmation.