Implement PauseStatusBanner coordinator widget
epic-peer-mentor-pause-ui-state-task-003 — Build the PauseStatusBanner widget for coordinator-facing mentor list and detail views. It must display paused status, the ISO-formatted timestamp of when the pause was applied, the optional reason text, and a reactivate shortcut button. Use design token colours and typography. Banner must be dismissible and re-renderable.
Acceptance Criteria
Technical Requirements
Implementation Notes
Place the widget at lib/features/peer_mentor/widgets/pause_status_banner.dart. Accept pausedAt as a DateTime and format it with intl package's DateFormat('d MMM yyyy, HH:mm') using the device locale. Accept reason as String? and use a conditional expression in build to include or exclude the reason Text.
The banner should be a pure StatelessWidget — dismissibility is controlled by the parent via an isVisible flag or by simply not including the banner in the tree. This makes it trivially re-renderable without internal state. For the Reactivate button, reuse the existing AppButton widget. Keep the dismiss button as a plain IconButton(icon: Icon(Icons.close)).
Wrap the entire banner in a Semantics widget with a constructed label: 'Mentor paused on [formatted date]. Reason: [reason or none]. Tap Reactivate to restore.'. Use ExcludeSemantics on individual child Text widgets if the parent Semantics label is comprehensive enough, to prevent double-reading by screen readers.
Testing Requirements
Write widget tests covering: (1) banner renders status label, timestamp, and reason when all are provided; (2) reason row is absent when reason is null; (3) reason row is absent when reason is empty string; (4) onReactivateTapped callback fires on tap of reactivate button; (5) onDismissed callback fires on tap of dismiss button; (6) timestamp is displayed in human-readable format (not raw ISO string); (7) design token color is applied to the status label (resolve from test theme); (8) Semantics label is non-empty and contains 'paused'. Run meetsGuideline(textContrastGuideline) via ensureSemantics(). Golden snapshot test for the fully-populated state.
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.
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.
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.