Implement mark-read and mark-all-read BLoC commands
epic-in-app-notification-centre-services-task-008 — Add MarkNotificationRead and MarkAllNotificationsRead event handlers to the NotificationBloc. Delegate mutation to the Notification Read State Service to get optimistic pre-emission, update the notification list in state with is_read=true and the received read_at timestamp, and decrement the unread count accordingly. On rollback signal from the service, revert state to the pre-mutation snapshot and emit an error notification.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Use the standard BLoC pattern: emit an optimistic state immediately, then await the service call. Store the pre-mutation state as a local variable before emitting. If the service returns a rollback signal (throws or returns a failure result type), emit the saved snapshot state followed by an error state/event. For MarkAllNotificationsRead, build the updated list with a single map() pass and emit once — avoid multiple emits.
Use copyWith on the Notification model for immutability. The read_at value should be extracted from the service response, not DateTime.now(). Consider using a sealed Result
Testing Requirements
Unit tests using flutter_test and bloc_test. Cover: (1) MarkNotificationRead emits optimistic state before service resolves, (2) state after successful mark-read has correct is_read and read_at values, (3) unread count decrements correctly, (4) MarkAllNotificationsRead sets all is_read=true and unread=0 in one emit, (5) rollback on service failure restores pre-mutation snapshot exactly, (6) error side-effect is emitted on rollback, (7) marking an already-read notification is a no-op on unread count. Use mocktail to mock the Notification Read State Service. Minimum 90% line coverage on the event handler code 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.