critical priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

BulkConfirmationScreen accepts a required ActivityTemplate and a required List<String> mentorIds as constructor parameters — it cannot be constructed without both
The screen renders a non-dismissible activity summary card at the top displaying: activity type name (resolved from activity_type_id), formatted date (e.g., 'Monday, 24 March 2026'), duration in minutes, and notes (or 'No notes' if null)
Below the summary card, a scrollable list renders one row per mentor (showing mentor display name or ID if name unavailable) from the mentorIds list
The mentor list is wrapped in a Scrollable/ListView — it must not overflow or clip on large batches (50+ mentors)
A mandatory review gate prevents submission: a 'Confirm' button is disabled until the user has scrolled to the bottom of the mentor list
The screen uses only design system card components (AppCard or equivalent) and design tokens for spacing, typography, and color — no hardcoded values
The screen is not registered as a named route in the router — it is pushed programmatically from the bulk registration flow only
The back button/gesture shows a confirmation dialog ('Cancel bulk registration?') before dismissing to prevent accidental loss of work
The screen is accessible: all card content is readable by screen readers (VoiceOver/TalkBack), summary card fields have semantic labels, and the scroll position is announced to accessibility services
The screen renders correctly on both small (375px width) and large (428px+) phone screen sizes without horizontal overflow

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
Supabase PostgreSQL 15 (to resolve activity_type name from activity_type_id)
data models
activity_type
activity
performance requirements
Screen must render within 300ms of navigation even for mentor lists of 100+ entries — use ListView.builder not ListView
Activity type name resolution must not block screen rendering — use a FutureBuilder or AsyncValue with a loading placeholder
security requirements
Mentor list must only display mentors belonging to the coordinator's organisation — validate via RLS on any data fetch
Activity template data rendered on screen must be escaped to prevent XSS-equivalent injection in text rendering
ui components
Activity summary card (non-dismissible, design system AppCard)
Mentor list row widget
Scroll-aware confirm button (disabled until scrolled to bottom)
Cancel confirmation dialog
Loading placeholder for activity type name resolution

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement the scroll-to-bottom gate using a ScrollController with a listener: when _scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 20 (20px tolerance), set a state flag _hasScrolledToBottom = true and call setState to enable the confirm button. Use ListView.builder for the mentor list with itemCount = mentorIds.length for O(1) rendering regardless of list size. Resolve the activity_type name using a FutureProvider (Riverpod) or FutureBuilder that queries Supabase for the activity_type record by ID — display a shimmer placeholder while loading. The cancel confirmation dialog should use Flutter's AlertDialog with 'Keep editing' and 'Cancel registration' options; pop the screen only on the latter.

Ensure all text content (type name, notes) goes through Flutter's standard Text widget — no HTML or raw string injection. For accessibility, wrap summary card fields in Semantics widgets with explicit labels (e.g., Semantics(label: 'Activity type: ${type.name}', child: ...)).

Testing Requirements

Widget tests: verify activity summary card renders all four fields (type, date, duration, notes). Verify 'No notes' fallback when notes is null. Verify confirm button is disabled on initial render. Verify confirm button enables after simulated scroll to bottom.

Verify back gesture triggers confirmation dialog. Verify ListView.builder is used (not ListView) by checking that only visible items are built. Accessibility tests: verify all summary card fields have Semantics labels. Verify the screen passes flutter_test's SemanticsHandle checks.

Golden tests: capture the initial layout with a 5-mentor list and a 50-mentor list to catch future regressions. Coverage target: ≥80% on BulkConfirmationScreen widget.

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.