high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

The dialog container is wrapped in a Semantics widget with scopesRoute: true and explicitChildNodes: true to establish it as an accessibility boundary
The dialog title Text widget has a Semantics wrapper with header: true so screen readers announce it as a heading
The consequence/description text has a Semantics wrapper with liveRegion: true so it is announced when the dialog opens
When the dialog opens, TalkBack (Android) moves focus to the dialog title heading automatically
When the dialog opens, VoiceOver (iOS) moves focus to the dialog title heading automatically
When the dialog is dismissed via Cancel, focus returns to the element that triggered the dialog (the Pause button in the parent screen)
All interactive elements (TextField, Confirm button, Cancel button) have distinct, non-empty semantics labels
The TextField has semanticsLabel 'Reason for pause, optional' or equivalent localized string
Running tester.ensureSemantics() + expectLater(tester, meetsGuideline(androidTapTargetGuideline)) passes in widget tests
Manual verification with TalkBack on Android emulator and VoiceOver on iOS simulator is documented in the PR description

Technical Requirements

frameworks
Flutter
flutter_test
performance requirements
Semantics annotations add zero runtime overhead in release builds (they are stripped in profile/release mode with semantics disabled)
security requirements
Live-region announcement must not read out any PII — only the dialog title and a static consequence message
ui components
Semantics widget (scopesRoute, explicitChildNodes, header, liveRegion)
FocusNode for managing focus restoration on dialog dismiss
ExcludeSemantics for decorative icons

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

In Flutter, use showDialog() with a builder that wraps the dialog in Semantics(scopesRoute: true, explicitChildNodes: true). Inside the dialog, wrap the title Text in Semantics(header: true). Wrap the consequence description in Semantics(liveRegion: true). For focus restoration, save a FocusNode reference to the triggering widget before opening the dialog (e.g., store in a variable in the parent), then in the onCancelled/onConfirmed callback call FocusScope.of(context).requestFocus(savedFocusNode).

In Flutter 3.x, FocusTraversalGroup can be used on the dialog to trap focus within it. Note: Flutter's AlertDialog already has some built-in semantics scaffolding — inspect the widget tree with SemanticsDebugger before adding manual Semantics nodes to avoid double-announcing content. Use ExcludeSemantics on any purely decorative icons.

Testing Requirements

Write widget tests using flutter_test: (1) verify Semantics node with scopesRoute=true exists in the widget tree when dialog is open; (2) verify the title Semantics node has header=true; (3) verify the liveRegion Semantics node is present on the consequence text; (4) run meetsGuideline(androidTapTargetGuideline) and meetsGuideline(textContrastGuideline) via tester.ensureSemantics(); (5) verify each interactive element has a non-empty semanticsLabel. Manual testing checklist: open dialog with TalkBack enabled — confirm title is announced first; dismiss with Cancel — confirm focus returns to trigger button. Document results in PR.

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.