high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

Widget loads the full list of available peer mentors from the contact repository on mount, showing a loading skeleton during fetch
Search field filters the visible list in real time (debounced at 300ms) by mentor name — case-insensitive, diacritic-tolerant
Tapping a mentor row toggles their selection state; selected mentors show a checkmark and highlighted background using design token accent color
Participant count badge updates synchronously on every selection change and displays '0 selected' through 'N selected'
Select-all action selects all mentors currently visible in the filtered list (not hidden by search)
Deselect-all action clears the entire selection regardless of current filter
Each mentor row touch target is at minimum 48×48 dp per WCAG 2.2 AA
All interactive elements (rows, select-all, deselect-all, search field) have Semantics labels readable by VoiceOver/TalkBack
Selected mentor IDs are exposed via a ValueNotifier or callback for parent widget consumption
Widget handles empty state (no mentors in org) with a descriptive message
Widget handles empty filtered results with a 'No mentors match your search' message

Technical Requirements

frameworks
Flutter
Riverpod
apis
Contact Repository (getAvailableMentors by coordinator chapter)
data models
MentorSummary
ContactProfile
performance requirements
List must remain smooth (60fps) with up to 200 mentor entries using ListView.builder
Search debounce of 300ms to avoid excessive rebuild cycles
Selection state updates must not rebuild the full list — use targeted setState or provider scoping
security requirements
Only mentors belonging to the coordinator's chapter/organization must be shown — enforce server-side filtering in repository query
Mentor IDs passed to parent must be validated UUIDs
ui components
MentorListTile (avatar, name, chapter, checkbox)
SearchTextField (design token variant)
ParticipantCountBadge
SelectAllBar (select-all / deselect-all row)
EmptyStateWidget
LoadingSkeletonList

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use `ListView.builder` with item extent set for constant-height rows to maximize scroll performance. Store selection as a `Set` (mentor IDs) rather than a list for O(1) lookup on toggle. For the search field, use a local `TextEditingController` with a `Timer`-based debounce rather than a stream to keep the implementation simple. The widget should be stateful internally but expose selected IDs upward via an `onSelectionChanged` callback so it remains composable.

For WCAG compliance, add `Semantics(label: '${mentor.name}, ${isSelected ? 'selected' : 'not selected'}, tap to toggle')` to each row. The select-all action should respect the current search filter — only select visible mentors — to avoid surprising coordinators who searched for a subset.

Testing Requirements

Widget tests: initial load renders skeleton then list, search filters correctly, select/deselect updates badge count, select-all selects all visible items only, deselect-all clears selection, empty state and empty filter state render correctly. Accessibility tests: verify minimum touch targets with tester.getRect(), verify Semantics tree includes labels for all interactive elements. Performance test with 200 mentor items verifying no frame budget overruns. Integration test on TestFlight verifying real data loads from Supabase.

Component
Mentor Multi-Select Widget
ui medium
Epic Risks (3)
high impact high prob scope

Partial failures in bulk registration — where some mentors succeed and others fail — create a complex UX state that is easy to mishandle. If the UI does not clearly communicate which records succeeded and which failed, coordinators may re-submit already-saved records (creating duplicates) or miss failed records entirely (creating underreporting).

Mitigation & Contingency

Mitigation: Design the per-mentor result screen as a primary deliverable of this epic, not an afterthought. Use a clear list view with success/failure indicators per mentor name, and offer a 'Retry failed' action that pre-selects only the failed mentors for resubmission.

Contingency: If partial failure UX proves too complex to deliver within scope, implement a simpler all-or-nothing submission mode for the initial release with a clear error message listing which mentors failed, and defer the partial-retry UI to a follow-up sprint.

medium impact medium prob technical

Submitting proxy records for a large group (e.g., 30+ mentors) as individual Supabase inserts may cause latency issues or hit rate limits, degrading the coordinator experience and potentially causing timeout failures that leave data in an inconsistent state.

Mitigation & Contingency

Mitigation: Implement the BulkRegistrationOrchestrator to batch inserts using a Supabase RPC call that accepts an array of proxy records, reducing round-trips to a single network call. Add progress indication using a stream of per-record results if the RPC supports it.

Contingency: If the RPC approach is blocked by Supabase limitations, fall back to chunked parallel inserts (5 records per batch) with retry logic, capping total submission time and surface a progress bar to manage coordinator expectations.

medium impact medium prob technical

Unifying state management for both single and bulk proxy flows in a single BLoC risks state leakage between flows — for example, a previously selected mentor list persisting when a coordinator switches from bulk to single mode — causing confusing UI states or incorrect submissions.

Mitigation & Contingency

Mitigation: Define separate, named state subtrees within the BLoC for single-proxy state and bulk-proxy state, with explicit reset events triggered on flow entry. Write unit tests for state isolation scenarios using the bloc_test package.

Contingency: If unified BLoC state becomes unmanageable, split into two separate BLoCs (ProxySingleRegistrationBLoC and ProxyBulkRegistrationBLoC) sharing only common events via a parent coordinator Cubit.