high priority medium complexity infrastructure pending infrastructure specialist Tier 2

Acceptance Criteria

RealtimeApprovalSubscription class exposes a single Stream<ApprovalQueueEvent> that merges INSERT and UPDATE events on the expense_claims table scoped to the coordinator's chapter
ApprovalQueueEvent is a sealed class with subtypes: ClaimSubmitted(ExpenseClaim), ClaimUpdated(ExpenseClaim, ClaimStatus previousStatus), ClaimActioned(String claimId, String actionedByCoordinatorId)
On subscription start, the stream immediately emits the current pending queue snapshot before switching to live events — so the UI can hydrate from a single source
Reconnection is handled automatically: on Supabase channel disconnection, the component re-subscribes with exponential backoff (max 30s delay) and emits a ReconnectingEvent to allow the UI to show a connectivity indicator
Channel is cleaned up (unsubscribed and removed) when dispose() is called — no memory leaks confirmed via Flutter DevTools widget rebuild test
Coordinator-scoped filtering: only events where chapter_id matches the coordinator's chapter are received — enforced by RLS and verified in tests
If another coordinator actions a claim, the stream emits a ClaimActioned event so the local queue can remove or update that item without a manual refresh
The component is injectable as a singleton per coordinator session (not recreated on screen navigation)
Connection status is exposed as a separate Stream<RealtimeConnectionStatus> with states: connected, reconnecting, disconnected

Technical Requirements

frameworks
Flutter
Dart
supabase_flutter (Realtime channels)
Riverpod StreamProvider or BLoC
apis
Supabase Realtime (postgres_changes)
Supabase PostgREST (initial snapshot fetch)
data models
ExpenseClaim
ApprovalQueueEvent
ClaimStatus
RealtimeConnectionStatus
performance requirements
Initial snapshot + first live event must be delivered to the UI within 1 second of subscription on a standard mobile connection
Backpressure handling: if events arrive faster than the UI can process, buffer up to 50 events before dropping oldest
Reconnection must not cause duplicate events — track last-processed event timestamp and discard earlier events on reconnect
security requirements
Channel name must include the coordinator's user_id to prevent channel collision between coordinators on the same device (shared device scenarios)
Never log claim content or PII in reconnection/error logs — log only event types and timestamps
RLS enforcement on Realtime must be verified — Supabase sends only rows matching the policy

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement RealtimeApprovalSubscription as a service class injected via Riverpod (keeping it outside the widget tree). Use a StreamController.broadcast() internally and merge the initial snapshot observable with the Realtime channel stream using StreamGroup.merge() from the async package. For reconnection, listen to the Supabase RealtimeChannel's onClose callback and schedule a re-subscribe using a Timer with exponential backoff. Use a Set of recently seen event IDs (last 5 minutes) to deduplicate events that may arrive twice after reconnect.

This component is the real-time backbone for the coordinator review queue screen — keep it infrastructure-only with no business logic; routing decisions (auto-approve, escalate) belong in task-005.

Testing Requirements

Unit tests: mock the Supabase Realtime channel and verify the stream emits the correct ApprovalQueueEvent subtype for each raw Postgres change payload. Test that dispose() unsubscribes the channel. Test reconnection logic: simulate a channel error and verify re-subscription with backoff. Test that duplicate events (same id, same updated_at) are deduplicated.

Integration tests: connect to a test Supabase instance, insert and update rows, and assert the stream emits the correct events in order. Verify no events leak across chapters using two different coordinator JWTs. Use flutter_test with fake async timers for backoff testing.

Epic Risks (3)
medium impact medium prob technical

Maintaining multi-select state across paginated list pages is architecturally complex in Flutter with Riverpod/BLoC. If the selection state is stored in the widget tree rather than the state layer, page transitions and list redraws can silently clear selections, causing coordinators to lose their multi-select and re-enter it.

Mitigation & Contingency

Mitigation: Store the selected claim ID set in a dedicated Riverpod StateNotifier outside the paginated list widget tree. The paginated list reads selection state from this provider and does not own it. Selection persists independently of list scroll position or page loads.

Contingency: If cross-page selection proves prohibitively complex, limit bulk selection to the currently visible page (add a clear warning in the UI) and prioritise single-page bulk approval for the initial release.

medium impact medium prob integration

If a coordinator has the queue open while another coordinator approves claims from the same queue (possible in large organisations with shared chapter coverage), the Realtime update may arrive out of order or be missed during a reconnect, leaving the first coordinator's view stale and allowing them to attempt to approve an already-actioned claim.

Mitigation & Contingency

Mitigation: The ApprovalWorkflowService's optimistic locking (from the foundation epic) will catch the concurrent edit at the database level. The CoordinatorReviewQueueScreen should handle the resulting ConcurrencyException by removing the claim from the local list and showing a brief snackbar: 'This claim was already actioned by another coordinator.'

Contingency: Add a queue staleness indicator (a subtle 'last updated X seconds ago' label) and a manual refresh button as a fallback for coordinators who notice inconsistencies.

low impact high prob dependency

The end-to-end test requirement that a peer mentor receives a push notification within 30 seconds of coordinator approval depends on FCM delivery latency, which is outside the application's control and can vary significantly in CI/CD environments.

Mitigation & Contingency

Mitigation: Structure end-to-end tests to verify notification intent (correct FCM payload dispatched, correct Realtime event emitted) rather than actual device delivery timing. Use test doubles for FCM delivery in automated tests and reserve real-device delivery tests for manual pre-release validation.

Contingency: If notification timing requirements must be validated in automation, instrument the ApprovalNotificationService with a test hook that records dispatch timestamps and assert against those rather than actual FCM callbacks.