high priority medium complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

BulkResultsScreen displays a summary card at the top showing: 'Submitted: N', 'Succeeded: N', 'Failed: N' with appropriate color tokens for each count
Below the summary card, a scrollable ListView renders one row per mentor with their name, status icon, and a short outcome message (e.g., 'Registered' or the server-returned error reason)
Succeeded rows use a success color token (green family); failed rows use an error color token (red family); colors sourced from design token system
A 'Done' button in the app bar or footer calls go_router.go() to the home/activity feed route, clearing the entire bulk registration stack from the navigation history
'Retry failed' button is visible only when failureCount > 0; tapping it navigates to BulkConfirmationScreen pre-populated with only the failed mentor entries
If all mentors succeeded, only the 'Done' button is shown and a success illustration or icon is displayed above the summary card
If all mentors failed, a prominent inline message advises the coordinator to check connectivity or contact support, in addition to the 'Retry failed' button
All status icons and color-coded rows have Semantics labels ('Succeeded for [mentor name]' / 'Failed for [mentor name]: [reason]') for screen reader accessibility
WCAG 2.2 AA contrast ≥ 4.5:1 for status text against row backgrounds
Screen is stateless — all data is received via route extra (BulkRegistrationResult); no additional API calls are made on this screen

Technical Requirements

frameworks
Flutter
go_router
data models
BulkRegistrationResult (successCount, failureCount, totalCount, perMentorOutcomes: List<MentorRegistrationOutcome>)
MentorRegistrationOutcome (mentorId, mentorName, success: bool, errorReason: String?)
performance requirements
Screen must render within one frame of navigation — no async calls on init
ListView must support up to 100 mentor rows without frame drops using ListView.builder
security requirements
Error reasons returned from the server must be displayed as-is but HTML-escaped to prevent any injection risk in the Flutter Text widget (Flutter handles this natively; confirm no use of HtmlWidget or similar)
No sensitive data (user IDs, tokens) is displayed or logged on this screen
ui components
ResultSummaryCard (stat chips: submitted/succeeded/failed)
MentorOutcomeRow (icon + name + status message + color coding)
RetryFailedButton (conditional visibility)
DoneButton (app bar action or bottom CTA)
AllSucceededIllustration (optional asset or Icon widget)

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Receive BulkRegistrationResult from the route extra via GoRouterState.extra cast; make the cast explicit with a runtime type check and redirect to home if the extra is missing (defensive navigation). The screen itself should be a StatelessWidget — derive all display data from the result object passed in. For 'Retry failed', construct the pre-populated mentor list from result.perMentorOutcomes.where((o) => !o.success) and pass it as extra to the BulkConfirmationScreen route (which should accept an optional initialMentors parameter). The 'Done' navigation should use go_router go() not pop() to ensure the entire bulk stack is cleared rather than just the top screen.

Source success/failure colors from design tokens (e.g., colorSuccess, colorError) to stay consistent with the WCAG-validated palette. Do not use a cubit for this screen; it is purely presentational.

Testing Requirements

Widget tests: mount BulkResultsScreen with a BulkRegistrationResult containing mixed outcomes; assert summary card shows correct counts; assert succeeded rows have success color and correct Semantics label; assert failed rows have error color and display error reason; assert RetryFailedButton is visible when failureCount > 0 and hidden when failureCount == 0; assert DoneButton triggers go_router navigation to home route. Edge-case widget tests: all-success result (no RetryFailedButton, illustration visible); all-failure result (advisory message visible). Navigation tests: tap 'Retry failed' asserts BulkConfirmationScreen pushed with pre-filtered failed mentor list. Golden tests: capture all-success and mixed-outcome states.

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.