critical priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

BulkConfirmationCubit exposes a sealed state with variants: idle, loading, success(BulkRegistrationResult), failure(String errorMessage)
On confirmTapped(), cubit sets state to loading, calls BulkRegistrationService.submitBatch(activeMentors), then transitions to success or failure
In loading state the confirmation screen shows a full-screen progress indicator overlaid on the mentor list (list is not scrollable during loading)
On failure, the cubit returns to idle state and surfaces an inline error banner at the top of the screen with the error message and a 'Retry' button — no navigation occurs
On success, cubit navigates to the BulkResultsScreen passing the BulkRegistrationResult via the router
Removal and undo operations (from task-012) are handled by dedicated cubit methods that are disabled while state is loading
The confirm button shows a CircularProgressIndicator replacing its label during the loading state
If BulkRegistrationService throws a network timeout, the failure message reads 'Connection timed out. Please check your internet and retry.'
The cubit is provided via BlocProvider at the BulkConfirmationScreen route level — not higher in the widget tree
State transitions are logged with the app's existing analytics/logging service for debugging

Technical Requirements

frameworks
Flutter
BLoC/Cubit
apis
BulkRegistrationService.submitBatch(List<BulkMentorEntry>) -> Future<BulkRegistrationResult>
data models
BulkConfirmationState (sealed)
BulkMentorEntry
BulkRegistrationResult (perMentorOutcomes, successCount, failureCount)
performance requirements
Service call must have a client-side timeout of 30 seconds before emitting a failure state
Loading overlay must appear within one frame of the confirm tap — no perceptible delay
security requirements
Submitted mentor payload must only contain IDs and activity template reference — no PII beyond what Supabase RLS already controls
Authentication token is injected via the Supabase client; cubit must not handle tokens directly
ui components
LoadingOverlay (AbsorbPointer + CircularProgressIndicator centered over list)
InlineErrorBanner (top-of-screen, dismissible, with Retry button)
ConfirmBatchButton (state-aware: label / spinner / disabled)

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Use a sealed class for BulkConfirmationState with Equatable for reliable BlocBuilder rebuilds. The loading overlay should use AbsorbPointer(absorbing: true) wrapping the entire scaffold body — this prevents swipe-to-dismiss and scroll events while the request is in flight without disabling widget focus (which would break screen readers). For the service call, wrap in a try/catch with a Future.timeout(Duration(seconds: 30)) and map DioException / SocketException to user-friendly messages via a thin error mapping utility shared with other cubits. Navigate to BulkResultsScreen using go_router with the BulkRegistrationResult serialised as extra — avoid passing complex objects through the route path.

Keep the cubit focused on orchestration; do not place business logic (e.g., duplicate filtering) inside the cubit — delegate to the service layer.

Testing Requirements

Unit tests (flutter_test with mocktail): mock BulkRegistrationService; test confirmTapped() transitions idle → loading → success with correct BulkRegistrationResult; test idle → loading → failure with error message preserved in state; test that removeMentor() is a no-op when state is loading; test timeout scenario emits failure with timeout message. Widget tests: in loading state assert AbsorbPointer blocks list interaction and CircularProgressIndicator is visible; in failure state assert InlineErrorBanner is visible with correct message and Retry button re-triggers confirmTapped(); in success state assert navigation to BulkResultsScreen occurred. Integration test: stub BulkRegistrationService at the repository layer and run the full confirm flow end-to-end within the widget tree.

Epic Risks (3)
medium impact medium prob scope

If the batch insert RPC returns a mix of successes and failures (e.g., 3 of 10 mentors fail due to constraint violations that slipped through application-level duplicate detection), the confirmation screen result state becomes ambiguous. A coordinator who sees '7 of 10 succeeded' may not know whether to manually register the 3 failures, retry, or escalate — leading to either duplicate registrations or silent underreporting.

Mitigation & Contingency

Mitigation: Design the Bulk Registration Service to return a strongly typed BulkRegistrationResult with per-mentor RegistrationOutcome (success | duplicate_detected | constraint_violation | permission_denied). Design the result screen to list each failed mentor with a specific, plain-language reason and a one-tap 'Retry for this mentor' action that pre-fills the activity wizard with the batch template for that individual.

Contingency: If per-mentor retry UI is too complex to deliver within the epic scope, fall back to displaying failed mentors with their error codes and instructing coordinators to use single-proxy mode for the failures. Document this as a known limitation in release notes and create a follow-up ticket for per-mentor retry in the next sprint.

medium impact medium prob dependency

The Proxy Activity Wizard must reuse the existing activity wizard step widgets (type, date, duration, notes) while injecting a proxy attribution banner and a different submission payload builder. If the existing wizard is not designed for composability, the proxy variant may require forking the widget tree, creating two maintenance-diverging codebases that will drift out of sync when the base wizard is updated (e.g., new activity types added, new mandatory fields).

Mitigation & Contingency

Mitigation: Before implementing the Proxy Activity Wizard, audit the existing activity wizard's architecture. If steps are already extracted as independent StatelessWidget/ConsumerWidget classes, compose them directly with a wrapping Column that injects the attribution banner. If they are tightly coupled inside a parent widget, refactor the existing wizard to accept a nullable ProxyContext parameter before starting the proxy variant — this refactor should be a prerequisite task in this epic.

Contingency: If refactoring the base wizard is blocked by unrelated in-flight work on that component, implement the proxy wizard as a full fork but create a shared StepWidgets library file that both the base wizard and proxy wizard import. Schedule a deduplication refactor as a tech-debt ticket in the next planning cycle.

medium impact medium prob technical

The bulk registration flow spans three sequential screens (multi-select → activity form → confirmation → result) with shared mutable state: the selected mentor list, the activity template, the per-mentor duplicate warnings, and the final submission result. Managing this state across screens without a well-designed Bloc risks state leaks, stale duplicate warning data after mentor removal, and confirmation screen inconsistencies if the user navigates back and changes the mentor selection.

Mitigation & Contingency

Mitigation: Define a single BulkRegistrationBloc (or Cubit) with explicit state transitions covering: MentorsSelected → ActivityTemplateCompleted → DuplicatesChecked → ConfirmationReady → Submitting → SubmissionResult. Each backward navigation event (e.g., 'Back' from confirmation to mentor selection) dispatches a ResetToMentorSelection event that clears downstream state. Unit test every state transition with edge cases including empty mentor list, all mentors having duplicates, and network failure during submission.

Contingency: If state management complexity causes persistent bugs in testing, simplify by passing state explicitly through Navigator arguments (immutable snapshots per screen) rather than a shared Bloc. This reduces flexibility but eliminates cross-screen state mutation bugs.