high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

MentorStatusBLoC constructor accepts a required MentorStatusService parameter (constructor injection, not service locator)
PauseMentorEvent handler: emits MentorStatusLoading, awaits MentorStatusService.pauseMentor(reason), emits MentorStatusSuccess(newStatus: MentorStatus.paused) on success
ReactivateMentorEvent handler: emits MentorStatusLoading, awaits MentorStatusService.reactivateMentor(), emits MentorStatusSuccess(newStatus: MentorStatus.active) on success
On MentorStatusService throwing an InvalidTransitionException (or equivalent), BLoC emits MentorStatusError(message: localised message, isInvalidTransition: true)
On MentorStatusService throwing a network/generic exception, BLoC emits MentorStatusError(message: generic localised message, isInvalidTransition: false)
BLoC uses on<PauseMentorEvent> and on<ReactivateMentorEvent> registration (not mapEventToState deprecated API)
No raw try/catch on Exception — only expected typed exceptions are caught; unexpected exceptions propagate to BLoC's onError
Handler is async and correctly uses emit inside the async body (not after BLoC is closed)

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc 8+)
Supabase
apis
MentorStatusService.pauseMentor(String reason)
MentorStatusService.reactivateMentor()
data models
MentorStatusEvent
MentorStatusState
MentorStatus
performance requirements
Emit Loading state within 1 frame of event dispatch so UI spinner appears immediately
Total round-trip (dispatch → Loading → Success/Error) must not block UI thread
security requirements
Do not log pause reason strings to console or analytics — contains potentially sensitive personal context
Validate reason string length against MentorPauseReasonMaxLength before dispatching to service

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use flutter_bloc 8.x on() handler registration — the mapEventToState API is deprecated. Inject MentorStatusService via required constructor parameter and store as final field. Use a try/catch that catches known typed exceptions first (on InvalidTransitionException catch (e)) then a general catch as fallback. For the service interface, define an abstract class IMentorStatusService so tests can inject a mock without depending on the concrete Supabase implementation.

Emit order matters: always emit Loading before the await, otherwise the UI may miss the loading state if the service responds synchronously in tests. Use isClosed guard before emitting after async operations: if (!isClosed) emit(...).

Testing Requirements

Unit tests with bloc_test package: use blocTest() for all scenarios. Test PauseMentorEvent happy path: expect [MentorStatusLoading(), MentorStatusSuccess(newStatus: MentorStatus.paused)]. Test ReactivateMentorEvent happy path: expect [MentorStatusLoading(), MentorStatusSuccess(newStatus: MentorStatus.active)]. Test PauseMentorEvent with service throwing InvalidTransitionException: expect MentorStatusError with isInvalidTransition == true.

Test network failure: expect MentorStatusError with isInvalidTransition == false. Mock MentorStatusService using Mockito or Mocktail. Assert BLoC closes without error after all tests.

Component
Mentor Status BLoC
infrastructure 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.