high priority medium complexity frontend pending frontend specialist Tier 16

Acceptance Criteria

ProxyActivityForm (with attribution banner) is embedded below the peer mentor selector in the scrollable body
Submit button dispatches SubmitProxyActivity(mentorId, activityFormData) event to BLoC when tapped, but only after form validation passes (FormKey.currentState!.validate())
During submission (BLoC loading/submitting state): submit button is disabled and shows a CircularProgressIndicator replacing the button label
On duplicate/conflict state from BLoC: DuplicateWarningDialog is shown with options 'Save Anyway' and 'Cancel'; 'Save Anyway' dispatches ConfirmDuplicateSubmit; 'Cancel' dispatches CancelSubmit
On success state from BLoC: screen navigates back (GoRouter.of(context).pop()) and a SnackBar with message 'Activity registered for [mentor name]' is shown on the previous screen
On error state from BLoC: an inline error banner appears at the top of the scrollable body with the error message and a dismiss/retry action; submit button re-enables
Form data is preserved in BLoC state so it is not lost if the duplicate warning dialog is dismissed
Error banner is accessible: wrapped in Semantics with liveRegion: true
All state transitions are handled via BlocListener (side effects) and BlocBuilder (UI state) — never both in the same BlocConsumer unless intentional

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase REST API (via repository layer — not called directly from widget)
data models
ActivityFormData
ProxyRegistrationState (submitting, conflict, success, error sub-states)
ProxyActivitySubmission (mentorId, activityFormData, coordinatorId)
performance requirements
Submission API call must have a timeout of 10 seconds with user-visible feedback
Navigation after success must complete within one frame after BLoC emits success state
security requirements
coordinatorId included in submission must always come from the authenticated Supabase session — never from form input
Form data must be sanitized before dispatch: strip leading/trailing whitespace from notes field
ui components
ProxyActivityForm (task-010 + task-011 attribution banner)
AppButton with loading state variant
DuplicateWarningDialog (AlertDialog with two actions)
Inline error banner (AnimatedContainer or simple Column item with design token error surface color)
SnackBar via ScaffoldMessenger on the parent route

Execution Context

Execution Tier
Tier 16

Tier 16 - 2 tasks

Can start after Tier 15 completes

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

Implementation Notes

Use BlocConsumer with separate listenWhen and buildWhen predicates to cleanly separate side effects (navigation, snackbar, dialog) from UI rebuilds. The DuplicateWarningDialog should be shown via showDialog — do not use a conditional widget in the tree for dialogs as it complicates state management. The SnackBar must be shown on the previous screen after pop, not on the current screen before pop; achieve this by passing a callback or using GoRouter's extra parameter to signal the parent screen to show the snackbar. Form validation (FormKey.currentState!.validate()) must be called before dispatching SubmitProxyActivity — do not let the BLoC perform form validation, as that is the form's concern.

Coordinate with the DuplicateWarningDialog from task-006 to ensure the dialog interface matches what is expected here.

Testing Requirements

Widget tests (flutter_test): stub BLoC in submitting state and verify button shows progress + is disabled; stub conflict state and verify DuplicateWarningDialog appears, tap 'Save Anyway' and verify ConfirmDuplicateSubmit event emitted, tap 'Cancel' and verify CancelSubmit event emitted; stub success state and verify navigation pop is called (use MockGoRouter); stub error state and verify inline error banner appears with error text. Unit tests: BLoC event handling for SubmitProxyActivity (success path, duplicate path, error path). Integration test: full flow — fill form, select mentor, submit, verify Supabase insert called with correct payload (use Supabase mock). Accessibility test: error banner announced as live region.

Component
Proxy Registration Screen
ui medium
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.