Implement rollback logic on read state mutation failure
epic-in-app-notification-centre-services-task-005 — Add rollback handling to the Notification Read State Service. When a repository mutation fails (network error, RLS rejection, timeout), restore the pre-mutation snapshot and emit a failure event with a human-readable error message. Ensure the rollback is idempotent so that retrying the same operation after a rollback does not corrupt state. Cover both single-item and bulk mark-all-read rollback paths.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
In the catch block of both `markAsRead` and `markAllRead`, retrieve the stored snapshot from `_snapshot[id]` (or the bulk snapshot map) and emit `ReadStateRollback(ids: [...], restoredStates: {...})` followed by `ReadStateFailure(message: _humanReadableError(e))`. Implement `_humanReadableError` as a switch on exception type: `SocketException` → 'No internet connection', `TimeoutException` → 'Request timed out, please retry', `PostgrestException` with code 42501 → 'You do not have permission to update this notification', default → 'Something went wrong, please try again'. Always remove IDs from `_inFlight` in a `finally` block to prevent permanent stuck state. Clear snapshot entries after use (both success and failure) to avoid memory growth.
For idempotency: check if notification is already in un-read state before applying rollback — if already rolled back, skip silently.
Testing Requirements
Unit tests (flutter_test) using mock repository that throws configurable exceptions. Test scenarios: (1) network error on single markAsRead triggers rollback of that item only, (2) timeout on markAllRead triggers bulk rollback of all optimistically updated items, (3) RLS rejection produces 'permission denied' human-readable message, (4) retry after rollback produces fresh optimistic update (verify snapshot is cleared and re-captured), (5) rollback with missing snapshot emits failure without throwing, (6) rollback is idempotent (call rollback handler twice, assert state unchanged on second call). Use fake_async to simulate timeouts. Verify in-flight set is empty after both success and failure paths.
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.