critical priority medium complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

Abstract class AnnouncementService defines the interface: announce(String message, {AnnouncementPriority priority, Locale? locale}), announceNow(String message), clearQueue(), dispose()
FlutterAnnouncementService implements AnnouncementService using SemanticsService.announce()
AnnouncementPriority enum has polite and assertive values; assertive bypasses the queue and announces immediately
Announcement queue processes one item at a time with configurable delay between announcements (default 500ms, configurable via AccessibilitySettings)
Locale-aware: if locale is provided, announcement uses that locale; otherwise falls back to device locale
announceNow() clears the queue and announces immediately (assertive behavior)
clearQueue() removes all pending announcements without announcing them
Facade is disposed cleanly (no timer leaks, queue cleared on dispose)
Registered as a Riverpod Provider<AnnouncementService> with the concrete implementation as default
Tests can substitute MockAnnouncementService via Riverpod override without any Flutter platform setup

Technical Requirements

frameworks
Flutter
Riverpod
apis
SemanticsService.announce
flutter/services MethodChannel (platform announcements)
data models
AnnouncementPriority
AnnouncementItem
performance requirements
Queue processing must not block the UI thread — use Future.delayed for spacing, not synchronous sleep
Assertive announcements must reach SemanticsService within 16ms of being requested
security requirements
Announcement text must not be logged in production builds — screen reader content may include sensitive field values

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement the queue as a List with a bool _isProcessing flag. Use a recursive Future-based processing loop (not a Timer.periodic) to process one item, wait the configured delay, then process the next. This avoids timer drift and is easier to test. For SemanticsService.announce(), note it is a static method — wrap it in a thin protected method so subclasses can override it in tests.

The platform difference between VoiceOver (iOS) and TalkBack (Android) is largely handled by Flutter's SemanticsService.announce() — you do not need separate MethodChannel calls unless you need custom announcement modes not exposed by Flutter. AnnouncementItem: {String message, AnnouncementPriority priority, Locale? locale}. Keep the Riverpod provider as Provider (not StateProvider) since the service manages its own mutable queue.

Testing Requirements

Write unit tests using flutter_test: (1) polite announcements are queued and processed in order, (2) assertive announcement bypasses queue, (3) clearQueue removes pending items, (4) dispose cancels timers and prevents further announcements, (5) locale is passed correctly to underlying service, (6) queue processes with correct delay between items. Use a mock SemanticsService or capture calls via the abstract interface. Widget tests should verify integration with the Riverpod provider override. Minimum 85% branch coverage on the facade implementation.

Component
Semantics Service Facade
infrastructure medium
Epic Risks (2)
high impact medium prob technical

Flutter's SemanticsService behaves differently between iOS (VoiceOver) and Android (TalkBack) in edge cases — e.g., announcement queuing, focus-gain timing, and attribute support. If the facade does not correctly abstract these differences, announcements may be silent or misfired on one platform, causing regression on the other platform to go unnoticed until device testing.

Mitigation & Contingency

Mitigation: Write platform-divergence unit tests early using SemanticsServiceFacade mocks. Validate announcement delivery on a physical iPhone (VoiceOver) and Android device (TalkBack) at the end of each sprint. Document known platform differences in the facade's inline API comments.

Contingency: If a platform difference cannot be abstracted cleanly, expose a platform-specific override path in the facade and implement targeted workarounds per platform, accepting the added complexity in exchange for correct behaviour.

medium impact medium prob scope

Accessibility preferences stored in local storage may need new fields as higher-tier epics are implemented (e.g., announcement verbosity, sensitive-field guard toggle). Schema changes to an already-persisted store risk data migration failures or silent defaults on existing installs, breaking user preferences.

Mitigation & Contingency

Mitigation: Design the AccessibilitySettingsRepository with a versioned JSON schema from the start, using merge-with-defaults on read so new fields fall back gracefully. Define the full expected field list upfront based on all downstream epic requirements before writing the first record.

Contingency: If migration fails on a live install, fall back to full reset-to-defaults with a one-time in-app notification informing the user that accessibility preferences have been reset and inviting them to reconfigure.