critical priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

The screen renders a scrollable list of all peer mentors from the same Proxy Contact List Provider used by the single selector; no duplicate Supabase fetch is triggered
Each row contains a visible checkbox on the leading edge, the mentor's full name as primary text, and relevant metadata as secondary text
Multiple rows can be checked simultaneously with no upper limit imposed at this screen level (bulk mode)
Checking and unchecking a row updates the selection set immediately with no perceptible delay
While the provider is loading, a loading indicator is shown instead of the list
When the contact list is empty, a plain-language empty state message is shown
When the provider returns an error, an inline error message with a retry action is shown
The current selection count is shown in the app bar or a summary strip (e.g., '3 selected') and updates in real time as checkboxes are toggled
The screen follows the project's existing page header and navigation patterns with a back button
The list is rendered with ListView.builder for performance

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
Proxy Contact List Provider (Riverpod) — same provider instance as single selector
data models
PeerMentor (id, fullName, metadata)
MultiSelectorState (selectedMentorIds: Set<String>)
AsyncValue<List<PeerMentor>>
performance requirements
Checkbox toggle must update only the affected row widget — use const constructors and fine-grained Riverpod providers or select() to avoid rebuilding the entire list
ListView.builder with itemExtent set to a fixed row height for smooth scrolling with 100+ contacts
Provider must be shared with single selector — no duplicate Supabase query
security requirements
Only peer mentors in the coordinator's own organisation are shown, enforced by Supabase RLS
Selected mentor IDs are held in memory only — not persisted to local storage at this stage
ui components
ConsumerWidget or ConsumerStatefulWidget (Riverpod)
ListView.builder with fixed itemExtent for performance
CheckboxListTile or custom row with Checkbox widget leading
Selection count badge/strip in AppBar or below AppBar
CircularProgressIndicator or skeleton for loading state
Empty and error state widgets (reuse or mirror patterns from single selector task-005)
AppBar with back button and selection counter

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Reuse the PeerMentorListTile widget created in task-005 and extend it to accept an onChanged callback and a isSelected boolean for the checkbox variant — this avoids duplicating row rendering logic. Manage the selection Set in a dedicated Riverpod StateNotifier (MultiSelectorNotifier) with add/remove/toggle methods. To prevent the entire list from rebuilding on each checkbox toggle, use Consumer at the row level watching only the specific mentor's selected state via provider.select((state) => state.selectedMentorIds.contains(mentorId)); this ensures only the toggled row rebuilds. The selection counter in the AppBar should watch only the selectedMentorIds.length via a select(), again to avoid full-screen rebuilds.

The Proxy Contact List Provider should be declared as a global/family provider so both this screen and the single selector share the same cached data — do not create a second provider with the same Supabase query.

Testing Requirements

Widget tests covering: (1) loading state — mock provider returns AsyncLoading, assert loading indicator present; (2) populated state — mock provider returns 3 mentors, assert all rows rendered with unchecked checkboxes; (3) empty state — assert empty state message; (4) error state — assert error message and retry; (5) checkbox toggle — tap row 1, assert selectedMentorIds contains row 1's ID; (6) multiple selection — tap rows 1 and 3, assert both IDs are in selectedMentorIds and rows 2 and 4 are unchecked; (7) deselect — tap a checked row, assert ID is removed from set; (8) selection count display — assert count label shows '2 selected' after two taps. Use Riverpod ProviderScope overrides with stubbed providers. Verify shared provider: assert same provider family key is used as in single selector (no duplicate).

Component
Peer Mentor Multi-Select List
ui medium
Epic Risks (2)
medium impact medium prob technical

NHF coordinators may manage dozens of peer mentors across multiple chapters. If the multi-select list renders all contacts in a single unsorted ListView, performance degrades with 50+ items, and coordinators cannot efficiently locate a specific mentor, increasing the probability of selection errors and wrong-person proxy registrations.

Mitigation & Contingency

Mitigation: Use a SliverList with itemExtent for fixed-height rows to enable O(1) scroll position calculation. Implement the search filter using a debounce utility operating on an in-memory list (no extra API calls). Sort the contact list alphabetically by default. Add chapter-filter chips above the list for NHF's multi-chapter coordinators.

Contingency: If performance issues arise in testing with real data sets, introduce pagination with a 'load more' trigger at the bottom of the list and cache rendered rows using Flutter's AutomaticKeepAliveClientMixin.

high impact medium prob security

NHF has a complex 12-national-association / 9-region / 1,400-chapter hierarchy. It is ambiguous whether a coordinator can proxy-register for peer mentors outside their immediately assigned chapter. If the contact list is not correctly scoped by RLS, coordinators might see — and register on behalf of — peer mentors they do not manage, creating fraudulent activity records that skew Bufdir statistics.

Mitigation & Contingency

Mitigation: The Proxy Contact List Provider must query only peer mentors linked to the coordinator's own chapter scope via RLS. Add an explicit Supabase query test asserting that a coordinator from chapter A cannot retrieve peer mentors from chapter B. Display each mentor's chapter affiliation in the list row so coordinators can visually verify scope.

Contingency: If RLS scope is found to be too permissive in testing, apply a server-side coordinator_id filter as a secondary guard on the query. Block the feature release until the scope test passes consistently.