high priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

When AccessibleModalSheet opens, VoiceOver (iOS) announces the modal's purpose string followed by 'dialog' (or equivalent locale-specific role label) without requiring the user to navigate to the title
TalkBack (Android) similarly announces the dialog role and purpose string on open
The announcement fires exactly once per open event — it does not repeat if the modal content rebuilds
The announcement is customisable via a `semanticLabel` parameter on AccessibleModalSheet, allowing different calling sites to provide context-specific descriptions (e.g., 'Activity registration dialog', 'Date selection dialog')
The Semantics container wrapping the modal has `namesRoute: true` and `label` set to the provided semantic label so that the Flutter accessibility bridge maps it to the OS dialog role
The live region announcement does not interfere with the initial focus placement implemented in task-009
The semantic role and announcement work in both Norwegian (primary locale) and English without hardcoded strings — uses localised label passed by the caller

Technical Requirements

frameworks
Flutter
flutter_test
performance requirements
Live region announcement must be dispatched after the open animation's first frame to avoid being suppressed by route transition audio events
security requirements
Semantic labels must not include sensitive personal data — callers are responsible for providing purpose-only strings
ui components
AccessibleModalSheet
Semantics(namesRoute: true, scopesRoute: true, label: semanticLabel)
SemanticsService.announce() for live region
TextDirection for RTL-safe label composition

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Wrap the modal content root in `Semantics(namesRoute: true, scopesRoute: true, label: widget.semanticLabel)`. This tells the Flutter accessibility bridge that the subtree is a named route/dialog scope, which maps to the ARIA `dialog` role on Android and to the UIAccessibilityTraitNone with custom label on iOS. In addition, call `SemanticsService.announce(widget.semanticLabel, TextDirection.ltr)` inside `addPostFrameCallback` on first open (guard with a `_announced` bool reset on each new open, not on rebuild). Coordinate timing with task-009's initial focus request: the announcement should fire on the same post-frame callback batch but after the focus request, so VoiceOver first says the dialog label, then shifts focus to the first element.

Accept `semanticLabel` as a required named parameter to force callers to provide meaningful context. Document clearly that this label should be a short, human-readable purpose description — not the modal title — and should not contain PII. Consider exporting an `AccessibleModalSheetDefaults` class with standard labels for common use cases (registration, date selection, confirmation) to encourage consistency across the app.

Testing Requirements

Write flutter_test widget tests that: (1) pump AccessibleModalSheet with a given `semanticLabel`, open it, and assert via tester.getSemantics() that the root SemanticsNode has `namesRoute: true`, `scopesRoute: true`, and the correct label; (2) trigger a content rebuild inside the modal and assert `SemanticsService.announce` was called exactly once (use a mock or spy on the semantics service); (3) verify that calling the modal with a different `semanticLabel` produces a different announcement string. Integration-test the full announcement on a physical device by observing TalkBack/VoiceOver output. Target ≥ 90% branch coverage for the announcement dispatch 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.