critical priority medium complexity frontend pending frontend specialist Tier 6

Acceptance Criteria

A search input field is displayed at the top of the mentor list (below the select-all row), with a placeholder text such as 'Search peer mentors'
Typing in the search field filters the visible list in real time with a 300 ms debounce; mentor items not matching the query are hidden
Previously selected mentors that are hidden by the search filter remain in the selectedMentorIds set — their selection is not cleared when the query changes
Clearing the search field restores the full list, with previously selected mentors showing as checked
The selected-count badge (from task-009) continues to reflect total selected items, including those hidden by the current search query
When selectedMentorIds is empty, the 'Continue' button is visually disabled (opacity reduction per design tokens) and is not focusable via keyboard/switch access
When selectedMentorIds is empty and the user taps the disabled 'Continue' button area (or taps an explicitly enabled 'Continue' with 0 selections), an inline error message appears: 'Please select at least one peer mentor to continue'
The inline error message disappears as soon as at least one mentor is selected
Tapping 'Continue' with one or more selected mentors navigates to the next screen in the bulk registration wizard and passes the full List<String> of selectedMentorIds as a route argument
The search field has a clear (×) button that appears when the field is non-empty and clears the query on tap
The search field meets WCAG 2.2 AA: correct label via Semantics, sufficient contrast, 44×44 dp touch target for the clear button

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
data models
PeerMentor
MentorSelectionState
BulkRegistrationWizardArgs
performance requirements
Search debounce of 300 ms prevents excessive BLoC events during rapid typing
Filtering logic runs on the full in-memory mentor list (already loaded) — no additional Supabase queries for search
ListView rebuild triggered only when filteredMentors list reference changes, using buildWhen in BlocBuilder
security requirements
Search query must be sanitized before any future server-side use; currently client-side only but sanitize defensively
Selected mentor IDs passed via route arguments must not be logged or exposed in error reports
ui components
MentorSearchBar (AppTextField variant with clear button)
InlineValidationError (existing or new Text widget styled with error color from design tokens)
AppButton (existing, with disabled state wired to selectedMentorIds.isEmpty)
BulkRegistrationWizardArgs (data class carrying List<String> selectedMentorIds)

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Add a searchQuery string field to MentorSelectionState. Dispatch a MentorSearchQueryChangedEvent from the search TextField's onChanged using a RxDart debounceTime or a manual Timer in the BLoC. The BLoC computes filteredMentors as a derived list from allMentors filtered by searchQuery; selectedMentorIds remains a Set independent of filtering. The 'Continue' AppButton's onPressed should be null (not a no-op) when selectedMentorIds.isEmpty to ensure Flutter marks it as disabled both visually and in the Semantics tree.

Inline error: use a Visibility or AnimatedSwitcher widget below the list that shows the error Text only when showValidationError is true in BLoC state; set showValidationError to true when user attempts navigation with empty selection. Route argument: define a BulkRegistrationWizardArgs class with final List selectedMentorIds; pass it via GoRouter extra or named route arguments to the next wizard screen.

Testing Requirements

Widget tests (flutter_test): (1) Render list with 5 mock mentors, type a query that matches 2 mentors, assert only 2 items visible. (2) Select 1 visible item, change query so that item is hidden, assert count badge still shows '1 selected' and the item remains in state. (3) Clear query, assert all 5 items visible and selected item shows checked. (4) With 0 selections, assert 'Continue' button has semantics label indicating disabled.

(5) With 0 selections, trigger continue action, assert inline error message appears in widget tree. (6) Select 1 item, assert inline error disappears. (7) Tap 'Continue' with 2 selected, assert navigator receives route push with correct selectedMentorIds argument. Debounce test: dispatch rapid text-change events, assert BLoC emits filter events only after 300 ms silence.

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.