critical priority medium complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

When the wizard advances to any step, VoiceOver/TalkBack announces the step name and ordinal in the format 'Step N of M: <step name>' within 300ms of the transition completing
The announcement is posted via LiveRegionAnnouncer using assertive polite priority so it does not interrupt ongoing speech
The Semantics widget for the step indicator exposes a progressValue (currentStep / totalSteps) readable by assistive technology
The step name announced matches the human-readable label shown visually (not the internal step ID)
The activity wizard supports exactly 5 steps: Contact, Date, Time, Duration, Summary — all 5 produce correct announcements
Announcements are NOT duplicated when the widget rebuilds for reasons unrelated to step transition (e.g., timer tick, BLoC state update that does not change the step index)
The component works correctly with both VoiceOver (iOS) and TalkBack (Android)
If LiveRegionAnnouncer is unavailable (null/mocked), the wizard degrades gracefully with no crash and no announcement
The announcement text is localised — Norwegian ('Trinn 2 av 5: Velg dato') and English both produce correct output
WCAG 2.2 AA criterion 4.1.3 (Status Messages) is satisfied: step transitions are programmatically determinable

Technical Requirements

frameworks
Flutter Semantics widget
SemanticsService.announce
BLoC for wizard step state
Riverpod for LiveRegionAnnouncer injection
apis
SemanticsService.announce(message, textDirection)
Semantics(value: ..., increasedValue: ..., decreasedValue: ...)
Flutter intl for localisation
data models
activity
activity_type
performance requirements
Step transition animation must complete before announcement fires — delay announcement by animation duration (typically 250-300ms)
No unnecessary Semantics tree rebuilds on non-step state changes
security requirements
Announcement text must not include PII (contact names are NOT announced in step transitions — only step metadata)
Audio announcements must stop when the app is backgrounded per DictationScopeGuard requirements
ui components
ActivityWizardStepSemantics wrapper widget
LiveRegionAnnouncer service
Step indicator with Semantics progressValue
Localised step label resolver

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use a BLoC listener (BlocListener) rather than didUpdateWidget to detect step changes — this avoids false triggers from widget rebuilds. Compare the previous state's step index to the new one; only call LiveRegionAnnouncer.announce() when they differ. Delay the announcement using a Future.delayed(animationDuration) so the screen reader does not compete with transition animation audio cues. For the progress value: wrap the step indicator row with Semantics(value: 'Step $current of $total', ...) rather than using the progressValue double, because VoiceOver on iOS reads percentage progress inconsistently — prefer a human-readable string value.

Store step labels in the localisation arb file, keyed as activity_wizard_step_contact, activity_wizard_step_date, etc. Inject LiveRegionAnnouncer through Riverpod so it can be mocked in tests without difficulty.

Testing Requirements

Unit tests: verify that given a step transition from step 1→2 in a 5-step wizard, the announcer produces 'Step 2 of 5: Select date'. Verify no announcement fires when step index is unchanged. Widget tests: use flutter_test SemanticsController to capture semantic announcements; assert progressValue is 0.4 on step 2 of 5. Verify LiveRegionAnnouncer is called exactly once per step transition.

Integration test: simulate a full 5-step wizard flow programmatically and assert all 5 transition announcements fire in the correct order. Localisation test: switch locale to Norwegian and verify announcement format. Accessibility audit: run Flutter's built-in semanticsChecker on the wizard page. Coverage target: 85% for announcer logic.

Component
Activity Wizard Step Semantics
ui medium
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.