critical priority high complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

When AccessibleModalSheet opens, screen reader focus is placed on the modal title or the first interactive element within 100 ms of the open animation completing
While the modal is open, VoiceOver (iOS) and TalkBack (Android) swipe navigation cannot reach elements behind the modal overlay
Physical and on-screen keyboard Tab/Shift-Tab focus is fully contained within the modal boundary
If the modal content is rebuilt or new interactive elements are inserted after the initial open, focus is not lost and remains within the modal
The modal container is annotated with a dialog semantics role so screen readers announce 'dialog' on entry
Closing the modal via the close button, back gesture, or dismiss does not crash when the modal has no focusable children
The focus trap is cleanly torn down when the modal is fully closed (no residual focus listeners)
Works correctly for all modal entry points used in the activity wizard (multi-step: contact → date → time → duration → summary)

Technical Requirements

frameworks
Flutter
flutter_test
performance requirements
Initial focus request must fire after the open animation frame completes — use addPostFrameCallback to avoid requesting focus before the widget is laid out
Focus trap listener overhead must not cause frame drops; dispose all listeners in deactivate/dispose
security requirements
The focus trap must not inadvertently suppress system-level accessibility announcements (e.g., incoming notification overlays)
ui components
AccessibleModalSheet
Semantics widget (scopesRoute: true, explicitChildNodes: true)
FocusScope / FocusNode
ModalBarrier (existing Flutter mechanism extended)
AutomaticKeepAliveClientMixin (if modal content uses PageView or TabBar internally)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use `Semantics(scopesRoute: true, explicitChildNodes: true)` at the root of the modal content to signal a dialog scope to the Flutter accessibility bridge. Create a root `FocusScope` node that is `canRequestFocus: true` and is automatically focused via `WidgetsBinding.instance.addPostFrameCallback(() => _focusNode.requestFocus())` inside `initState`. To prevent focus escaping, override `onKey` / `onKeyEvent` in the FocusScope and intercept Tab/Shift-Tab keys, re-routing to the first/last focusable descendant when the edge is reached. For assistive-technology traversal (swipe), `scopesRoute: true` is the primary mechanism in Flutter's accessibility bridge — verify this actually traps TalkBack on Android as the bridge behaviour differs between OS versions.

If TalkBack still escapes, use `ExcludeSemantics` on the `ModalBarrier` beneath the sheet to remove page content from the semantics tree while the modal is open. Handle dynamic content by listening to a `ValueNotifier` that increments on content change and re-requesting focus via the post-frame callback if focus has left the modal subtree.

Testing Requirements

Write flutter_test widget tests that: (1) pump AccessibleModalSheet with a known set of interactive children, trigger open, and assert via tester.getSemantics() that focus is on the title node; (2) simulate swipe-past-last-element and assert focus wraps back to the first element inside the modal, not to the page behind; (3) inject new child widgets after open and assert focus remains inside the modal. Add golden tests to verify the semantics tree structure. Integration-test on device using Accessibility Inspector (iOS) and Accessibility Scanner (Android) to confirm dialog role announcement. Cover the edge case of an empty-content modal.

Target ≥ 90% statement coverage for focus management logic.

Component
Accessible Modal Sheet Widget
ui high
Epic Risks (3)
high impact high prob integration

Flutter does not natively enforce a focus trap within a bottom sheet or modal dialog in the semantic tree — VoiceOver and TalkBack can navigate outside the sheet to background content. Implementing a reliable focus trap requires overriding the semantic tree, which may conflict with the existing modal helper infrastructure in the app and require changes to shared components beyond this feature's scope.

Mitigation & Contingency

Mitigation: Prototype the focus trap on the first modal sheet implementation before building the remaining sheets. Evaluate Flutter's ExcludeSemantics and BlockSemantics widgets as the trap mechanism, and coordinate with the team owning the shared modal helpers to agree on a non-breaking integration point before writing production code.

Contingency: If a complete semantic focus trap cannot be implemented without breaking existing modal patterns, implement a partial solution using FocusScope with autofocus on the modal's first element and a prominent 'Return to main content' semantic action, documenting the deviation from WCAG 2.4.3 with a scheduled remediation item.

high impact medium prob technical

The activity wizard uses BLoC state management and the UI rebuilds the entire step widget subtree on transition. If the semantic tree is traversed by VoiceOver before the build cycle settles, focus may land on a stale or partially rendered step, causing the wrong step label or progress value to be announced. This is particularly problematic for blind users who cannot visually verify the announcement against the screen.

Mitigation & Contingency

Mitigation: Coordinate ActivityWizardStepSemantics with FocusManagementService (from the core services epic) to delay focus placement until the post-build callback confirms the new step's semantic tree is complete. Write integration tests using the AccessibilityTestHarness that assert the full announcement sequence across all five wizard steps.

Contingency: If post-build focus delay is insufficient due to async BLoC emission timing, add an explicit semantic notification barrier in the wizard cubit that emits a 'step ready' event only after the new widget tree has been marked as built, decoupling the announcement trigger from the raw state transition.

medium impact medium prob scope

Automated WCAG contrast ratio checking on widget tree snapshots may produce false positives for gradient backgrounds, dark-mode overrides, or design token overrides that are resolved at runtime but appear as unresolvable colours at static analysis time. Excessive false positives would erode team trust in the CI gate, leading to suppression rules that also mask real violations.

Mitigation & Contingency

Mitigation: Scope the WCAGComplianceChecker to check only solid-colour backgrounds in the first iteration, explicitly excluding gradients from contrast checks with documented rationale. Design the check output to distinguish 'undetermined' (gradient/unknown) from 'fail' (solid colour below threshold) so the team can take targeted action on genuine failures only.

Contingency: If false positive rates exceed 20% of reported violations during initial CI runs, switch the CI gate from a hard build failure to a warning annotation on the pull request, combined with a mandatory manual review step, until the checker's rule set has been tuned to match actual design token values.