high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Status badge is wrapped in Semantics(liveRegion: true, label: 'Status: Active' / 'Status: Paused') so VoiceOver/TalkBack announces the change automatically when it updates
Action button Semantics label includes current state: 'Pause mentor — currently active' or 'Reactivate mentor — currently paused'
Action button has Semantics(button: true) so assistive technologies expose it as a button role
Active badge colour pair (text on background) achieves ≥ 4.5:1 contrast ratio (WCAG 2.2 AA normal text)
Paused badge colour pair (text on background) achieves ≥ 4.5:1 contrast ratio (WCAG 2.2 AA normal text)
No duplicate focus stops — badge and button each receive exactly one focus stop in the screen reader traversal order
Semantics traversal order matches visual top-to-bottom order (badge first, then button)

Technical Requirements

frameworks
Flutter
flutter_test
data models
MentorStatus
performance requirements
Live region announcement must fire within one frame of state change — no debounce delay
ui components
Semantics (liveRegion: true)
Semantics (button: true, label: dynamic string)
PauseReactivateToggle

Execution Context

Execution Tier
Tier 2

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.

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.