high priority low complexity frontend pending frontend specialist Tier 12

Acceptance Criteria

BulkParticipantList is a StatelessWidget with two required parameters: `List<BulkParticipant> participants` and `void Function(BulkParticipant) onRemove`
BulkParticipant is a typed Dart class (or freezed data class) with at minimum: id (String), displayName (String), hasConflict (bool, default false)
When participants list is empty, the widget renders a visible empty-state placeholder with the text 'No participants selected' (or equivalent copy from org labels system)
When participants list is non-empty, a ListView.builder renders one row per participant showing the participant's displayName
Each row reserves a trailing slot (placeholder Container or SizedBox) for future action controls (remove button, conflict badge) — the slot is present in the widget tree even if visually empty
The widget uses only design token values for spacing, typography, and colors — no hardcoded hex values or numeric padding constants
Widget is wired into the bulk registration screen (or a dedicated Storybook-equivalent test page) and renders correctly with a mock list of 3 participants
Widget has a semantic label for accessibility: the ListView has a `semanticsLabel` or equivalent Semantics wrapper
No BLoC or Riverpod logic inside BulkParticipantList itself — it is a pure presentational widget driven entirely by its constructor parameters

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
data models
BulkParticipant
performance requirements
Use ListView.builder (not ListView with children) to support large participant lists without building all rows upfront
Widget rebuild must be triggered only when the participants list reference changes — use const constructors where possible
security requirements
Participant names rendered in the list must be HTML-escaped if rendered in any WebView context (not applicable for native Flutter rendering, but note for future web target)
ui components
BulkParticipantList (StatelessWidget)
BulkParticipantRow (StatelessWidget, extracted sub-widget for each row)
EmptyParticipantPlaceholder (StatelessWidget)

Execution Context

Execution Tier
Tier 12

Tier 12 - 5 tasks

Can start after Tier 11 completes

Dependencies (21)
epic-proxy-activity-registration-orchestration-task-001 component Cross-Epic Component bulk-participant-list depends on proxy-registration-service
epic-proxy-activity-registration-orchestration-task-002 component Cross-Epic Component bulk-participant-list depends on proxy-registration-service
epic-proxy-activity-registration-orchestration-task-003 component Cross-Epic Component bulk-participant-list depends on proxy-registration-service
epic-proxy-activity-registration-orchestration-task-004 component Cross-Epic Component bulk-participant-list depends on proxy-registration-service
epic-proxy-activity-registration-orchestration-task-005 component Cross-Epic Component bulk-participant-list depends on proxy-registration-service
epic-proxy-activity-registration-orchestration-task-010 component Cross-Epic Component bulk-participant-list depends on proxy-registration-service
epic-proxy-activity-registration-orchestration-task-006 component Cross-Epic Component bulk-participant-list depends on bulk-registration-service
epic-proxy-activity-registration-orchestration-task-007 component Cross-Epic Component bulk-participant-list depends on bulk-registration-service
epic-proxy-activity-registration-orchestration-task-008 component Cross-Epic Component bulk-participant-list depends on bulk-registration-service
epic-proxy-activity-registration-orchestration-task-009 component Cross-Epic Component bulk-participant-list depends on bulk-registration-service
epic-proxy-activity-registration-orchestration-task-011 component Cross-Epic Component bulk-participant-list depends on bulk-registration-service
epic-proxy-activity-registration-core-services-task-001 component Cross-Epic Component bulk-participant-list depends on proxy-duplicate-detection-service
epic-proxy-activity-registration-core-services-task-002 component Cross-Epic Component bulk-participant-list depends on proxy-duplicate-detection-service
epic-proxy-activity-registration-core-services-task-003 component Cross-Epic Component bulk-participant-list depends on proxy-duplicate-detection-service
epic-proxy-activity-registration-core-services-task-008 component Cross-Epic Component bulk-participant-list depends on proxy-duplicate-detection-service
epic-proxy-activity-registration-core-services-task-004 component Cross-Epic Component bulk-participant-list depends on activity-attribution-service
epic-proxy-activity-registration-core-services-task-005 component Cross-Epic Component bulk-participant-list depends on activity-attribution-service
epic-proxy-activity-registration-core-services-task-006 component Cross-Epic Component bulk-participant-list depends on activity-attribution-service
epic-proxy-activity-registration-core-services-task-007 component Cross-Epic Component bulk-participant-list depends on activity-attribution-service
epic-proxy-activity-registration-core-services-task-009 component Cross-Epic Component bulk-participant-list depends on activity-attribution-service
epic-proxy-activity-registration-core-services-task-010 component Cross-Epic Component bulk-participant-list depends on activity-attribution-service

Implementation Notes

Place the widget at `lib/features/bulk_registration/widgets/bulk_participant_list.dart` and the model at `lib/features/bulk_registration/models/bulk_participant.dart`. Keep BulkParticipantRow as a private sub-widget in the same file initially — extract to its own file only when it grows beyond ~60 lines. Use the app's existing `AppDesignTokens` (or equivalent constants class) for all spacing and typography values to stay consistent with the rest of the UI. The empty-state placeholder should use the same pattern as other empty states in the app (check Contacts list for reference).

Do not use `key` parameters unless required for animation — add them in task-003 when Dismissible is introduced. Register BulkParticipant as an equatable or freezed class so that list comparison works correctly in BLoC state.

Testing Requirements

Widget tests using flutter_test and WidgetTester. Test file: `test/widgets/bulk_participant_list_test.dart`. Required test cases: (1) empty list renders empty-state text, (2) list of 3 participants renders 3 rows with correct names, (3) tapping the trailing slot area does not throw (future-proofing), (4) widget rebuilds correctly when participants prop changes. Use `find.text()` and `find.byType()` for assertions.

Pump the widget inside a MaterialApp with a mock design token theme. Target: 100% branch coverage for BulkParticipantList.

Component
Bulk Participant List Widget
ui low
Epic Risks (4)
medium impact high prob scope

The bulk registration screen combines pre-filled defaults, a dynamic multi-select participant list, per-participant conflict badges, and a batch submission confirmation — making it one of the most complex screens in the application. Scope creep or underestimated interaction complexity could cause the epic to exceed its estimate significantly.

Mitigation & Contingency

Mitigation: Implement the bulk screen in two vertical slices: (1) participant selection and form submission without conflict handling, (2) conflict badge rendering and override flow. Validate slice 1 with coordinators before building slice 2.

Contingency: If the full conflict review UI cannot be completed within the epic, ship the bulk screen with a simplified 'skip all duplicates' fallback mode and defer per-participant override toggles to a follow-up sprint.

medium impact medium prob technical

The proxy-registration-bloc must manage state across two distinct flows (single proxy and bulk) with branching conflict paths, intermediate buffering of bulk participant selections, and reliable state reset. Incorrect state transitions could leave the UI in a loading or stale-conflict state after submission.

Mitigation & Contingency

Mitigation: Model the BLoC state as a sealed class hierarchy with exhaustive pattern matching in the UI. Write state-machine unit tests that exercise every valid transition and assert that invalid transitions are no-ops. Use flutter_bloc's BlocObserver in debug builds to log all transitions.

Contingency: If BLoC state bugs surface in QA, introduce an explicit ResetToIdle event triggered on screen disposal to guarantee clean state, and add a 'Start over' affordance visible to the coordinator at any conflict step.

high impact medium prob technical

The typeahead peer mentor selector with multi-select mode may be difficult to operate with VoiceOver/TalkBack, particularly the dynamic search results list and the selected-chip removal controls, risking WCAG 2.2 AA non-compliance for screen reader users.

Mitigation & Contingency

Mitigation: Wrap all search result items and selected chips with explicit Semantics widgets providing role, label, and selected-state announcements. Test the selector with VoiceOver on iOS and TalkBack on Android during development, not only at QA time.

Contingency: If the multi-select typeahead cannot be made fully accessible within the sprint, provide an alternative flat scrollable list with checkboxes as a fallback mode, toggled by an accessibility-settings flag.

high impact medium prob security

The peer mentor selector must apply RLS chapter-scope filtering to show only mentors the coordinator is responsible for. If the Supabase query for the selector does not correctly join against the coordinator's chapter assignments, coordinators may see mentors from other chapters, violating data isolation.

Mitigation & Contingency

Mitigation: Implement the selector's data query using the same RLS-aware Supabase client used by the contact list feature, which already handles chapter-scope filtering. Write an integration test with a multi-chapter coordinator fixture asserting cross-chapter mentors are not returned.

Contingency: If a data isolation breach is discovered, immediately add a client-side chapter_id filter as a defence-in-depth measure and audit selector query logs for any unauthorised cross-chapter results.