high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

PauseConfirmationDialog is shown as a modal via showDialog() and cannot be dismissed by tapping outside (barrierDismissible: false)
Dialog body clearly explains the consequences of pausing in plain language (e.g., 'You will no longer appear in the coordinator's active mentor list')
An optional TextField is present for the peer mentor to enter a free-text reason; field is not required to confirm
A primary 'Confirm pause' button calls the onConfirmed(String? reason) callback with the current TextField value (null if empty)
A secondary 'Cancel' button calls the onCancelled() callback and closes the dialog
Both buttons meet the WCAG 2.2 AA minimum touch target of 48×48 dp
Text contrast ratio between dialog body text and background is ≥4.5:1 (WCAG 2.2 AA normal text)
Button label contrast ratio is ≥4.5:1 against button background
Dialog uses design tokens for all colors, spacing, and typography — no hardcoded values
The widget is a StatefulWidget only if needed to manage TextField controller; otherwise StatelessWidget with externally provided controller
Dialog is fully dismissible via the Cancel button; pressing device back button also triggers onCancelled callback

Technical Requirements

frameworks
Flutter
flutter_test
data models
PauseReason (String?, optional)
performance requirements
Dialog must animate open in ≤300ms using Flutter's default dialog animation
No async operations inside the dialog widget itself — async work is handled by the caller after callback
security requirements
Free-text reason field must enforce a maximum character limit (e.g., 500 chars) to prevent excessively large payloads
No PII should be logged from the reason field
ui components
AlertDialog or custom Dialog scaffold
AppTextField (existing reusable widget) for reason input
AppButton (existing reusable widget) for confirm and cancel
Design token typography for title and body
Semantics wrapper (handled in task-002)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Place the widget at lib/features/peer_mentor/widgets/pause_confirmation_dialog.dart. Use a showPauseConfirmationDialog(BuildContext context) helper function that calls showDialog() with barrierDismissible: false — this keeps the caller code clean. Internally, use a StatefulWidget with a TextEditingController for the reason field, disposed in dispose(). Reuse existing AppButton and AppTextField widgets to maintain visual consistency and reduce test surface.

For the back button, wrap the Dialog content in WillPopScope (or PopScope on Flutter 3.16+) that calls onCancelled and returns false to prevent automatic pop. Do not put any BLoC or Riverpod calls inside this widget — it must be pure UI with callbacks, making it easily testable and reusable across coordinator and mentor contexts.

Testing Requirements

Write widget tests covering: (1) dialog renders title, body text, and reason field; (2) confirm button triggers onConfirmed with null when reason field is empty; (3) confirm button triggers onConfirmed with typed text when reason field has content; (4) cancel button triggers onCancelled; (5) dialog cannot be dismissed by tapping the barrier; (6) device back button triggers onCancelled. Also write a contrast check test using flutter_test's AccessibilityGuidelines: tester.ensureSemantics() + expectLater(tester, meetsGuideline(textContrastGuideline)). No integration tests required for this widget alone.

Component
Pause Confirmation Dialog
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.