high priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

PauseReactivateToggle is wrapped in a BlocBuilder<MentorStatusBLoC, MentorStatusState> that rebuilds only when state type or newStatus changes (buildWhen condition)
During MentorStatusLoading, the action button is replaced by a CircularProgressIndicator of the same size (no layout shift)
During MentorStatusSuccess, the badge colour reflects newStatus from the success payload — not a stale local variable
On MentorStatusError, a SnackBar is shown via BlocListener with the error message; if isInvalidTransition is true, the snackbar uses a distinct warning style
Tapping 'Pause' opens PauseConfirmationDialog; on dialog confirmation callback the reason string is passed to PauseMentorEvent dispatch
Tapping 'Reactivate' dispatches ReactivateMentorEvent directly without a dialog
BlocListener and BlocBuilder are combined using BlocConsumer to avoid nested widget overhead
Button is disabled (non-interactive) while MentorStatusLoading is active — prevents double-dispatch
After successful state change, focus is returned to the action button for screen reader continuity

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
flutter_test
data models
MentorStatusState
MentorStatus
PauseMentorEvent
ReactivateMentorEvent
performance requirements
buildWhen must prevent unnecessary rebuilds — only rebuild on Loading↔Success↔Error transitions, not on unrelated state emits
SnackBar must appear within one frame of Error state emit
security requirements
Reason string from PauseConfirmationDialog must be validated (non-empty, within max length) before dispatching PauseMentorEvent
ui components
BlocConsumer<MentorStatusBLoC, MentorStatusState>
PauseReactivateToggle
PauseConfirmationDialog
CircularProgressIndicator
SnackBar (ScaffoldMessenger)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use BlocConsumer with both listener (for side effects: SnackBar) and builder (for UI rebuild). Set buildWhen: (previous, current) => current is MentorStatusLoading || current is MentorStatusSuccess || current is MentorStatusError to avoid rebuilding on unrelated states. For the loading spinner, use SizedBox(width: buttonWidth, height: 44, child: Center(child: CircularProgressIndicator.adaptive())) to preserve layout dimensions. For the dialog flow: await the showDialog result — if the returned reason is null (user cancelled), do not dispatch.

Use ScaffoldMessenger.of(context).showSnackBar() inside the BlocListener — never inside the builder. Guard the dispatch call: if (context.read().state is! MentorStatusLoading) before dispatching to prevent double-tap race. After success, use FocusScope to restore focus to the action button for WCAG 2.2 focus management compliance.

Testing Requirements

Widget tests with MockBloc (using bloc_test / mocktail): emit MentorStatusLoading and assert spinner visible + button disabled. Emit MentorStatusSuccess(newStatus: paused) and assert badge shows 'Paused'. Emit MentorStatusError(message: 'Network error', isInvalidTransition: false) and assert SnackBar appears. Emit MentorStatusError with isInvalidTransition: true and assert warning-style SnackBar.

Tap 'Pause' and assert PauseConfirmationDialog opens. Confirm dialog and assert PauseMentorEvent dispatched with correct reason string. Tap 'Reactivate' and assert ReactivateMentorEvent dispatched. Integration test: full flow from tap → dialog → dispatch → Success state update using a real BLoC with a mocked MentorStatusService.

Component
Pause / Reactivate Toggle
ui low
Epic Risks (3)
medium impact medium prob technical

PauseConfirmationDialog must meet WCAG 2.2 AA focus trap requirements: when the dialog opens, focus must move to the first interactive element; when it closes, focus must return to the triggering toggle. Flutter's default showDialog does not always guarantee correct focus restoration on Android, which could trap screen-reader users.

Mitigation & Contingency

Mitigation: Use a custom modal implementation wrapping Flutter's Dialog with explicit FocusScope management and test with TalkBack on Android and VoiceOver on iOS during development, not just in final QA. Reference the accessible-modal-sheet pattern already used elsewhere in the codebase.

Contingency: If full WCAG focus management cannot be achieved within the sprint, ship the feature with a documented accessibility defect and schedule a dedicated accessibility remediation task. Communicate the known gap to the accessibility stakeholder at HLF/NHF.

low impact medium prob technical

If the user taps the PauseReactivateToggle rapidly before the BLoC emits a loading state, multiple PauseMentorEvents could be added to the BLoC stream, resulting in duplicate service calls and inconsistent UI state (toggle flickering between states).

Mitigation & Contingency

Mitigation: Disable the toggle widget immediately on first tap by emitting MentorStatusLoading synchronously before the async service call begins. Use BLoC's event transformer with droppable() to discard subsequent events while a transition is in progress.

Contingency: If BLoC event deduplication is not sufficient, add a debounce at the widget level (300 ms) and a server-side idempotency check in MentorStatusService that no-ops if the requested transition is already in progress.

low impact low prob security

PauseStatusBanner must conditionally render the reactivate shortcut only for users with coordinator permission. If the permission check relies on a stale role state in the Riverpod provider, a peer mentor could briefly see a reactivate action they are not authorised to use, which could cause a confusing permission-denied error if tapped.

Mitigation & Contingency

Mitigation: Source the permission check directly from the role-resolution Riverpod provider (which is kept in sync with Supabase auth) rather than from local component state. Add a widget test asserting the shortcut is absent when the user role is peer_mentor.

Contingency: If the shortcut is accidentally shown to an unauthorised user, the underlying MentorStatusService enforces role validation server-side, so the worst outcome is a visible error message rather than an actual unauthorised state change.