high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Each active mentor row exposes a removal affordance: a trailing icon button (delete/close icon) that is always visible, supplemented by swipe-to-dismiss on supporting platforms
Tapping the remove button immediately transitions the mentor row to a 'removed' visual state (strikethrough text, reduced opacity) without leaving the screen
Removed mentors are grouped in a collapsible 'Removed' section below the active list, each showing an 'Undo' button
Tapping 'Undo' moves the mentor back to the active list and increments the batch count
The confirm button label reads 'Confirm for N mentors' where N reflects only the currently active (non-removed) mentor count
The confirm button is disabled and shows 'No mentors selected' when all mentors have been removed
Removal and undo actions are reflected in BulkConfirmationCubit state — not local widget state
Swipe-to-dismiss includes a red background with a trash icon visible during the swipe gesture
All removal/undo controls have Semantics labels readable by screen readers: 'Remove [mentor name] from batch' and 'Undo removal of [mentor name]'
Rapid repeated removal of mentors does not cause list index errors or duplicate entries in either section

Technical Requirements

frameworks
Flutter
BLoC/Cubit
data models
BulkMentorRowState (active | removed)
BulkConfirmationState (activeMentors, removedMentors)
performance requirements
Removal animation must complete within 300 ms
List reconstruction after removal must not jank; use AnimatedList or SliverAnimatedList for smooth item transitions
security requirements
Removal is a local UI operation only — no API calls are made until the coordinator taps Confirm
The cubit must not allow submitting a payload that includes mentors marked as removed
ui components
Dismissible wrapper with directional swipe configuration
MentorRemoveButton (trailing IconButton)
RemovedMentorsSection (collapsible ExpansionTile or AnimatedList)
UndoButton per removed mentor row
ConfirmBatchButton with dynamic label derived from cubit state

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Model the cubit state with two separate lists: activeMentors and removedMentors, both typed as List. The confirm payload is assembled only from activeMentors. Use Dismissible with direction: DismissDirection.endToStart for the swipe gesture and confirmDismiss callback to dispatch the cubit event rather than relying on onDismissed alone — this prevents the widget from auto-removing before the state update. For the ConfirmBatchButton, derive the label string directly inside a BlocBuilder that rebuilds only when the activeMentors count changes (use select to narrow the rebuild scope).

Avoid AnimationController in the row widget itself; let Dismissible and AnimatedList handle motion. The 'Removed' section header should be hidden with AnimatedSize when removedMentors is empty to avoid visual clutter for coordinators who do not remove anyone.

Testing Requirements

Unit tests: BulkConfirmationCubit.removeMentor(mentorId) moves mentor from activeMentors to removedMentors; BulkConfirmationCubit.undoRemoval(mentorId) reverses the move; active count updates correctly after each operation; confirm payload excludes removed mentors. Widget tests: tap remove button on row N, assert row moves to removed section and confirm button label updates; tap undo, assert row returns to active section; remove all mentors, assert confirm button is disabled; swipe-to-dismiss gesture test using WidgetTester.drag. Accessibility: verify Semantics labels on remove and undo controls via SemanticsController.

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.