Add WCAG 2.2 AA semantics to PauseReactivateToggle
epic-peer-mentor-pause-ui-state-task-006 — Annotate PauseReactivateToggle with Semantics: the status badge must have role=status and announce changes via a live region; the action button must have a descriptive label that includes current state (e.g., 'Pause mentor — currently active'). Verify colour contrast ratios for both active and paused badge variants.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Flutter's Semantics widget supports liveRegion: true which maps to aria-live='polite' on web and accessibilityLiveRegion on Android. On iOS, VoiceOver responds to liveRegion via UIAccessibilityPostNotification — test on a real iOS device or simulator with VoiceOver enabled as the simulator behaviour can differ. For the button label, compute it as a method: String _buttonSemanticLabel(MentorStatus status) => status == MentorStatus.active ? 'Pause mentor — currently active' : 'Reactivate mentor — currently paused'; Expose this as a @visibleForTesting getter for unit testability.
Contrast ratios for design tokens should be documented in the design system; if a token fails 4.5:1, escalate to design — do not override the token value in the widget.
Testing Requirements
Unit/widget tests: use tester.getSemantics() to assert liveRegion == true on badge node. Widget tests: assert button Semantics label equals expected state-contextual string for both active and paused states. Manual accessibility test: toggle status while VoiceOver active — badge change must be announced without user interaction. Contrast check: programmatically compute contrast ratio of design token badge colours in a Dart test using WCAG formula (luminance); assert >= 4.5.
Flutter semantics debugger check: run app with --debug-semantics and confirm no unexpected ExcludeSemantics wrappers suppress badge/button.
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.