high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

VoiceOver (iOS) and TalkBack (Android) announce the full pause message as a single coherent sentence including status, timestamp, and reason when the banner receives focus
The reactivate shortcut button has a Semantics label of 'Reactivate mentor — currently paused' (or equivalent locale string) and semanticsRole Button
The banner is wrapped in a MergeSemantics or Semantics(container: true) node so sub-elements are not announced separately
Dynamic type scaling at 200% (iOS Accessibility > Larger Text) does not clip or truncate any text — layout reflows to multi-line
The banner is not announced when the mentor is active (widget is absent from tree) — no phantom focus stops
Colour contrast between banner text and background meets WCAG 2.2 AA (minimum 4.5:1 for normal text, 3:1 for large text)
No redundant aria-hidden or ExcludeSemantics on informational content within the banner

Technical Requirements

frameworks
Flutter
flutter_test
flutter_semantics
data models
MentorStatusState
PauseDetails (timestamp, reason)
performance requirements
Semantics tree update must complete within one frame (16 ms) after state change
security requirements
Pause reason text must be sanitised before being passed to Semantics label to prevent injection of control characters
ui components
Semantics (Flutter widget)
MergeSemantics
PauseStatusBanner
Flexible/Expanded for dynamic type reflow

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use Semantics(container: true, label: '...') with a computed label string that concatenates status + formatted timestamp + reason. Do NOT rely on child widget labels being merged automatically — build the composite label in the parent Semantics node. For the reactivate button use Semantics(button: true, label: 'Reactivate mentor — currently paused', child: ...). Wrap the entire banner in a LayoutBuilder + FittedBox pattern only if needed for overflow; prefer Flexible inside a Column.

Use MediaQuery.textScalerOf(context) (Flutter 3.12+) instead of deprecated textScaleFactor. Avoid ExcludeSemantics on the timestamp/reason — screen-reader users need this information. Design token colours must already satisfy contrast; if not, file a design system issue rather than overriding inline.

Testing Requirements

Unit tests: verify Semantics node properties (label, role, container=true) using flutter_test's SemanticsHandle and tester.getSemantics(). Widget tests: render banner with long reason strings at textScaleFactor 2.0 and assert no overflow errors. Widget tests: confirm reactivate button Semantics.button == true and label contains 'Reactivate'. Integration/manual: test with VoiceOver on iOS simulator — banner must be announced as single unit.

Accessibility audit with flutter_test's SemanticsController.checkSemantics() must pass.

Component
Pause Status Banner
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.