critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

Calling `getContactsForCoordinator(coordinatorId)` returns a merged, deduplicated list of the coordinator's assigned members and the peer mentors they supervise
Deduplication is by `contact.id` — a contact appearing in both sub-queries is included exactly once in the result
When a coordinator has no assigned members and no supervised peer mentors, the method returns an empty list without throwing an error
When only one sub-query returns results (e.g. members exist but no peer mentors), the method returns only the non-empty set correctly
The result list is sorted consistently (e.g. alphabetical by contact display name) to ensure deterministic ordering for the UI
The implementation calls `ContactRepository.getAssignedMembers(coordinatorId)` and `ContactRepository.getSupervisedPeerMentors(coordinatorId)` — no raw Supabase queries in the service layer
Both sub-queries are executed concurrently using `Future.wait` to minimise total latency
Any exception thrown by either sub-query is propagated to the caller (no silent swallowing) and includes context about which sub-query failed
The implementation passes all unit tests written in task-001 structural tests and new tests written for this task
dart analyze reports no issues on the implemented file

Technical Requirements

frameworks
Flutter
Riverpod
apis
ContactRepository.getAssignedMembers
ContactRepository.getSupervisedPeerMentors
Supabase (via repository layer only)
data models
Contact
CoordinatorAssignment
PeerMentorSupervision
performance requirements
Both sub-queries execute concurrently via Future.wait — total latency equals max(query1, query2), not sum
Deduplication runs in O(n) using a Set<String> on contact IDs, not O(n²) list comparison
security requirements
coordinatorId must be validated as non-empty before dispatching queries
The method must not return contacts outside the coordinator's scope — scope enforcement is done in the repository layer, but the service must not bypass it
Row-level security (RLS) in Supabase is the authoritative enforcement layer; service-level filtering is a defence-in-depth measure

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use `Future.wait([repo.getAssignedMembers(id), repo.getSupervisedPeerMentors(id)])` to issue both queries concurrently. After awaiting, merge with a `LinkedHashSet` keyed on `contact.id` to deduplicate while preserving insertion order, then convert back to a sorted list. Sort by `contact.displayName.toLowerCase()` for consistent ordering. For error propagation, let the exception from `Future.wait` bubble naturally — do not catch and rethrow unless you need to add context.

If adding context is desired (e.g. for observability), use `throw ContactListServiceException('Coordinator contact fetch failed', cause: e)` with a typed exception class. Avoid loading both results into a flat list before deduplicating — this wastes memory on large contact sets. The coordinator's own contact record should NOT be included in the result (the list shows contacts they manage, not themselves).

Testing Requirements

Write unit tests using flutter_test with a FakeContactRepository: (1) both sub-queries return results — assert merged and deduplicated list, (2) a contact appears in both sub-queries — assert it appears exactly once in output, (3) both sub-queries return empty — assert empty list returned, (4) only members sub-query returns results — assert correct list, (5) only peer mentors sub-query returns results — assert correct list, (6) one sub-query throws — assert exception propagates with context, (7) assert Future.wait concurrency by recording call order in the fake. Coverage target: 100% on getContactsForCoordinator. All tests must be deterministic.

Component
Contact List Service
service 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.