high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

MentorMultiSelectWidget renders a list of peer mentors filtered to the coordinator's chapter, sourced from the existing mentor repository
A search TextField at the top filters the list in real-time with a 300ms debounce — no search fires until 300ms after the user stops typing
Selected mentors appear as dismissible Chip widgets in a wrap row above the list; tapping the chip's delete icon deselects that mentor
A 'Select All' button selects all currently visible (post-filter) mentors up to the 50-mentor limit
Selecting more than 50 mentors is blocked — the 51st tap is ignored and a snackbar/tooltip explains the limit
Each mentor list item shows at minimum: full name and role/chapter label
All mentor list items and chips have Semantics labels readable by screen readers (VoiceOver/TalkBack) — e.g., 'Select [Name], peer mentor' and '[Name], selected, double-tap to remove'
An empty state is shown when the search query matches no mentors ('No mentors found for "[query]"')
The widget exposes a ValueChanged<List<String>> onSelectionChanged callback that fires whenever the selection set changes
Widget test (task-011) can render the widget with mock mentor data and simulate search, select, deselect, and select-all without errors

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Existing peer mentor repository (filtered by coordinator chapter ID)
data models
PeerMentorSummary (id, name, chapterId, role)
performance requirements
List must use ListView.builder — never a Column with children for the mentor list
Debounce must prevent redundant filtering on every keystroke — use a Timer-based debounce or RxDart
Chip wrap area must not cause layout thrash — use a fixed max-height container with internal scroll if chips overflow
security requirements
Mentor list must only include mentors belonging to the coordinator's own chapter — filter enforced both in repository query and as a UI-level assertion
Widget must not expose mentor IDs in accessibility labels — use names only for screen reader output
ui components
MentorMultiSelectWidget (StatefulWidget or ConsumerStatefulWidget)
MentorListItem (card row widget: name + role label + checkbox)
SelectedMentorChip (dismissible Chip with name)
AppTextField (existing shared text field) for search input
SelectAllButton (text button with count indicator)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use a ConsumerStatefulWidget to watch the mentor list from a Riverpod provider, filtered by the coordinator's chapterId. Store selected mentor IDs in a local Set _selectedIds in the widget state — Set gives O(1) lookup for the checkbox state. Implement debounce using a Timer: cancel the previous timer on each keystroke and start a new 300ms timer. For the chip row, wrap Chips in a Wrap widget inside a ConstrainedBox with maxHeight to avoid unbounded growth pushing the list off screen.

The select-all button should operate on the current _filteredList (post-search) so it is context-aware. Ensure the widget is fully self-contained with no hardcoded chapter ID — receive it via constructor parameter. Register the mentor data provider in Riverpod so tests can override it with a mock list without touching real Supabase. Keep the widget reusable: it should work in any flow that needs multi-mentor selection, not just the bulk proxy flow.

Testing Requirements

Widget tests (task-011) must cover: (1) initial render shows full mentor list from mock data, (2) typing in search input filters list after 300ms debounce, (3) tapping a mentor item adds a chip and checks the item, (4) tapping chip delete removes selection and unchecks item, (5) select-all selects all visible items up to 50, (6) selecting 51st item is blocked, (7) empty state shows when no match found, (8) onSelectionChanged emits correct list on each change, (9) semantics tree contains correct labels. Use flutter_test with fake async for debounce timing tests and mock the mentor data source via a Riverpod override.

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

Supabase RLS policies for org-scoped proxy access may be difficult to express correctly, especially for coordinators with multi-chapter access. An overly permissive policy could allow cross-org proxy registrations, corrupting Bufdir reporting; an overly restrictive policy could block legitimate coordinators from registering.

Mitigation & Contingency

Mitigation: Write integration tests covering all access boundary cases (same org, cross-org, multi-chapter coordinator) before merging any RLS migration. Use parameterised RLS test helpers already established by the auth feature.

Contingency: If RLS proves insufficient, add a server-side Edge Function validation layer that re-checks org membership before persisting any proxy record, providing defence in depth.

medium impact low prob technical

Adding new tables and foreign key constraints to an existing production Supabase database risks migration failures or locking issues if the database already contains active sessions during deployment.

Mitigation & Contingency

Mitigation: Use additive-only migrations (no DROP or ALTER on existing tables). Test full migration sequence in a staging Supabase project before production deployment. Schedule during low-traffic window.

Contingency: Maintain a rollback migration script. If the migration fails, the feature remains unreachable behind a feature flag while the schema issue is resolved.

high impact medium prob security

Audit log entries must be immutable for compliance, but Supabase RLS by default allows row owners to update their own rows. If audit records are accidentally mutable, dispute resolution and accountability guarantees are invalidated.

Mitigation & Contingency

Mitigation: Configure the proxy_audit_log table with an RLS policy that allows INSERT for coordinators but denies UPDATE and DELETE for all roles including service_role, enforced at the database level.

Contingency: If RLS cannot fully prevent updates, create a database trigger that reverts any UPDATE to the audit table and logs the attempt as a security event.