critical priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

ContactListRiverpodProvider extends AsyncNotifier<ContactListState> (or equivalent typed notifier) using Riverpod's code-gen or manual provider declaration
On first read, the notifier triggers ContactListService.fetchForRole() and sets state to AsyncValue.loading() before the fetch completes
On successful fetch, state transitions to AsyncValue.data(contacts) with the full role-scoped list
On fetch error, state transitions to AsyncValue.error(e, st) preserving the stack trace
A separate `peerMentorContacts` AsyncValue is exposed — either as a second provider that reads this one, or as a second field on the state class
The peer mentor contacts stream is only populated when the authenticated user is a peer mentor; for other roles it is AsyncValue.data([])
Calling `refresh()` on the provider triggers a fresh fetch and transitions back through loading → data/error
The provider is invalidated and re-fetched when the authenticated user context changes (e.g., role switch)
Widget tests confirm: loading spinner shown during AsyncValue.loading, contact list shown on AsyncValue.data, error widget shown on AsyncValue.error
No direct Supabase calls inside the provider — all data access goes through ContactListService

Technical Requirements

frameworks
Flutter
Riverpod (AsyncNotifier, ref.watch, ref.invalidate)
flutter_riverpod
apis
ContactListService.fetchForRole(UserContext)
ContactListService.fetchPeerMentorContacts(UserContext)
data models
Contact
UserContext
ContactListState (fullList: AsyncValue<List<Contact>>, peerMentorList: AsyncValue<List<Contact>>)
performance requirements
Provider must not trigger duplicate fetches when multiple widgets watch it simultaneously
State transitions must complete within one frame to avoid UI jank (AsyncValue transitions are synchronous)
Auto-dispose should be enabled to free resources when no widget is watching
security requirements
Provider must derive user context from a trusted auth provider (e.g., authStateProvider), not from widget parameters
Sensitive contact data in state must not be persisted to local storage by the provider
ui components
ContactListLoadingWidget
ContactListErrorWidget
ContactListView

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Use `@riverpod` code generation (riverpod_generator) if the project already uses it — this enforces correct AsyncNotifier patterns and eliminates boilerplate errors. If not, use `AsyncNotifierProvider` manually. Model the state as a dedicated `ContactListState` class rather than a bare `List` — this allows adding the peer mentor list, search results, and future fields without breaking the provider signature. The `build()` method should `ref.watch(authStateProvider)` to automatically re-run when auth changes, enabling automatic re-fetch on role switch.

Use `ref.onDispose()` to cancel any in-flight futures when the provider is disposed. For the peer mentor list: either derive it from the full list using a `FutureProvider.family` that filters, or fetch it separately in `build()` — the separate fetch approach is cleaner if peer mentor contacts are a distinct query.

Testing Requirements

Unit tests using ProviderContainer (riverpod_test package) to verify state transitions without UI. Test: initial state is AsyncValue.loading, then transitions to data after service resolves, then to error when service throws. Widget tests (flutter_test + WidgetTester) to verify UI responds correctly to each AsyncValue state. Mock ContactListService using Riverpod's overrideWithValue or a Mockito mock.

Test role-based peer mentor list population. Test provider invalidation triggers re-fetch. Aim for 100% coverage of state transition paths.

Component
Contact List Riverpod Provider
data medium
Epic Risks (2)
medium impact medium prob technical

For organizations with large contact lists (NHF has 1,400 local chapters and potentially thousands of contacts), local in-memory filtering may be too slow and Supabase ILIKE queries without supporting indexes may exceed acceptable response times or accumulate excessive read costs, degrading search usability for power users.

Mitigation & Contingency

Mitigation: Define and document the list-size threshold in ContactSearchService before implementation. Confirm that indexes on name and notes columns exist in the Supabase schema before enabling server-side search. Profile ContactSearchService against realistic data volumes in the staging environment using the largest expected org.

Contingency: If response times are unacceptable in staging, introduce result-count pagination in ContactListService and add a user-visible 'showing top N results — refine your search' indicator, deferring full pagination to a follow-up task.

high impact low prob security

In NHF's multi-chapter context, when a user switches organization, Riverpod providers may emit a brief window of stale contact data scoped to the previous organization before the invalidation cycle completes, transiently exposing contacts from the wrong chapter.

Mitigation & Contingency

Mitigation: Model organization context as a Riverpod provider dependency so that any context change immediately marks contact providers as stale. Render a loading skeleton instead of the stale list during the re-fetch transition. Cover this scenario in integration tests with explicit org-switch sequences.

Contingency: If race conditions are observed during QA, add an explicit organization_id equality check in ContactListService that compares each fetched record's scope to the active session org, discarding any mismatched batch before returning results to the provider.