high priority low complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

AccessibilityLiveRegionAnnouncer exposes `Future<void> announce(String message, {bool assertive = false})` as its sole public method
Polite announcements are debounced with a 500ms window — only the last message in the window is dispatched
Assertive announcements bypass the debounce and are dispatched immediately
Dispatching uses `SemanticsService.announce(message, textDirection)` with TextDirection.ltr as default
The class is registered as a Riverpod `Provider` (singleton scope) named `accessibilityLiveRegionAnnouncerProvider`
The class implements a `dispose()` method that cancels any pending debounce timers
Empty string messages are silently ignored (no dispatch, no error)
The class is testable by accepting a SemanticsService abstraction via constructor injection
WCAG 2.2 AA live region behavior is respected: polite waits for idle, assertive interrupts

Technical Requirements

frameworks
Flutter
Riverpod
apis
Flutter SemanticsService.announce()
Flutter TextDirection
performance requirements
Debounce timer must not block the UI thread — use Timer from dart:async
No memory leaks: pending Timer is cancelled on dispose()
security requirements
Must not announce sensitive field values (PII, health data) — callers are responsible for filtering, but add a debug assertion warning in debug mode if message length exceeds 200 chars

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define a `SemanticsServiceWrapper` abstract class with a single `announce(String, TextDirection)` method. Provide a `RealSemanticsServiceWrapper` that delegates to `SemanticsService.announce()`. Inject the wrapper via constructor — the Riverpod provider supplies the real implementation, tests supply a mock. The debounce implementation: store a `Timer?

_debounceTimer` field; on each polite call, cancel existing timer and start a new 500ms timer. For assertive, call the wrapper directly and also cancel any pending polite timer to avoid a stale polite message following the assertive one. This is the WCAG-correct behavior: assertive takes priority. TextDirection should be configurable at construction time (default ltr) to support future RTL localization.

Testing Requirements

Unit tests with flutter_test and a mock SemanticsService. Test cases: (1) single polite announce dispatches after debounce delay, (2) two rapid polite announces dispatch only the second message, (3) assertive announce dispatches immediately without waiting for debounce, (4) assertive announce does not cancel a pending polite announce timer, (5) empty string is not dispatched, (6) dispose() cancels pending timer so no dispatch occurs after disposal, (7) provider returns the same instance on repeated ref.read() calls (singleton). Use fake_async package to control Timer without real delays.

Component
Accessibility Live Region Announcer
infrastructure low
Epic Risks (3)
high impact medium prob technical

iOS 15 on-device speech recognition has a 1-minute session limit and requires network fallback for longer sessions. Peer mentor way-forward dictation may routinely exceed this limit, causing silent truncation of transcribed content without user feedback.

Mitigation & Contingency

Mitigation: Implement session-chunking logic in NativeSpeechApiBridge that automatically restarts recognition before the limit is reached, preserving continuity via partial concatenation. Document the iOS 15 vs iOS 16 on-device recognition behaviour difference in code comments.

Contingency: If chunking causes user-visible interruptions, surface a non-blocking informational banner on iOS 15 devices informing users that very long dictation sessions may need to be broken into segments, and use PartialTranscriptionRepository to persist each chunk immediately.

high impact medium prob scope

On iOS, speech recognition permission can only be requested once. If the user denies the permission, the app cannot re-request it. A poor first-impression permission flow will permanently disable dictation for those users, impacting the Blindeforbundet blind-user base who rely on dictation most.

Mitigation & Contingency

Mitigation: Design the NativeSpeechApiBridge permission flow to show a clear pre-permission rationale screen before the OS dialog. Implement a graceful degradation path that hides the microphone button and shows a settings deep-link when permission is permanently denied.

Contingency: If users have already denied permission before the rationale screen is added, provide a settings deep-link in DictationScopeGuard's denial message directing users to iOS Settings > Privacy > Speech Recognition to re-enable manually.

medium impact low prob integration

The approved field IDs and screen routes configuration in DictationScopeGuard may fall out of sync with the actual report form schema as new fields are added by org administrators, silently blocking dictation on legitimately approved fields.

Mitigation & Contingency

Mitigation: Source the approved field configuration from the same org-field-config-loader used by the report form, rather than a hardcoded list. Add a developer-time assertion that logs a warning when a dictation-eligible field type is rendered but not in the approved routes map.

Contingency: Provide a runtime override mechanism in the scope guard that coordinators or admins can use to temporarily whitelist a field ID while the config is updated, with an automatic expiry.