high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

When DictationScopeGuard.isAvailable returns false, the widget renders SizedBox.shrink() (zero size, no visual presence, no accessibility tree entry)
When DictationScopeGuard.isAvailable returns true, the existing button renders correctly as per task-001
Toggling availability at runtime (e.g., user grants mic permission while the screen is open) causes the button to appear without requiring a full widget rebuild of the parent TextField
The parent TextField layout does not shift or reflow when the button appears or disappears — trailing icon slot is reserved or the field gracefully adapts
Unavailability due to org feature flag disabled is handled identically to unavailability due to missing OS permission — the widget has no knowledge of the reason, only the boolean result
Unavailability due to unsupported OS version is handled identically
No Riverpod ProviderException is thrown when the availability provider emits an error — widget falls back to hidden state gracefully
Widget test confirms the button is excluded from the semantics tree when hidden (ExcludeSemantics or SizedBox.shrink ensures no phantom accessibility node)

Technical Requirements

frameworks
Flutter
Riverpod
apis
DictationScopeGuard availability provider
data models
DictationAvailability (bool or sealed class)
performance requirements
Visibility change must not trigger a rebuild of widgets outside DictationMicrophoneButton's subtree
Availability provider must use select() or a dedicated boolean provider to avoid full ScopeGuard state rebuilds
security requirements
Widget must not expose why dictation is unavailable in its UI — no error messages or tooltip explaining 'missing permission' visible to end user at this layer
ui components
SizedBox.shrink() for hidden state
AnimatedSize or Visibility with maintainSize: false for smooth collapse

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

The cleanest approach is a dedicated `dictationAvailableProvider(fieldId)` (or a non-family global if availability is org-wide rather than per-field) that DictationScopeGuard exposes. Inside DictationMicrophoneButton, wrap the existing button in: `final isAvailable = ref.watch(dictationAvailableProvider); if (!isAvailable) return const SizedBox.shrink();`. Avoid `Visibility` widget with `maintainState: true` as it keeps the provider subscription alive unnecessarily. Consider whether `AnimatedSize` is appropriate for the appear animation — a simple fade is preferred over a sliding expand to avoid layout jumps.

If the parent TextField uses `suffixIcon`, note that Flutter's InputDecoration does not handle null suffixIcon gracefully in all themes — test on both material and the app's custom theme.

Testing Requirements

Widget tests: (1) Override dictationAvailabilityProvider with false — verify find.byType(IconButton) returns zero matches and find.byType(SizedBox) with zero size is present. (2) Override with true — verify IconButton is present. (3) Use StreamProvider or StateProvider in test to simulate a runtime availability change — pump after change and verify widget appears. (4) Override availability provider to throw an exception — verify widget renders SizedBox.shrink() and no exception propagates to the widget tree.

(5) Verify that when hidden, the Semantics tree contains no node with 'dictation' in the label.

Component
Dictation Microphone Button
ui low
Epic Risks (3)
medium impact medium prob technical

Merging dictated text at the current cursor position in a TextField that already contains user-typed content is non-trivial in Flutter — TextEditingController cursor offsets can behave unexpectedly with IME composition, emoji, or RTL characters, potentially corrupting the user's existing notes.

Mitigation & Contingency

Mitigation: Implement the merge logic using TextEditingController.value replacement with explicit selection range calculation rather than direct text manipulation. Write targeted widget tests covering edge cases: cursor at start, cursor at end, cursor mid-word, existing content with emoji, and content that was modified during an active partial-results stream.

Contingency: If cursor-position merging proves too fragile for the initial release, scope the merge behaviour to always append dictated text at the end of the existing field content and add the cursor-position insertion as a follow-on task after the feature is in TestFlight with real user feedback.

high impact medium prob technical

VoiceOver on iOS and TalkBack on Android handle rapid sequential live region announcements differently. If recording start, partial-result, and recording-stop announcements arrive within a short window, they may queue, overlap, or be dropped, leaving screen reader users without critical state information.

Mitigation & Contingency

Mitigation: Implement announcement queuing in AccessibilityLiveRegionAnnouncer with a minimum inter-announcement delay and priority ordering (assertive recording start/stop always takes precedence over polite partial-result updates). Test announcement behaviour on physical iOS and Android devices with VoiceOver/TalkBack enabled as part of the acceptance test plan.

Contingency: If platform differences make reliable queuing impossible, reduce partial-result announcements to a single 'transcription updating' message with debouncing, preserving the critical start/stop announcements. Coordinate with the screen-reader-support feature team to leverage the existing SemanticsServiceFacade patterns already established in the codebase.

medium impact low prob integration

The DictationMicrophoneButton must integrate with the dynamic-field-renderer which generates form fields from org-specific schemas at runtime. If the renderer does not expose a stable field metadata API for dictation eligibility checks, the scope guard and button visibility logic will require invasive changes to the report form architecture.

Mitigation & Contingency

Mitigation: Coordinate with the post-session report feature team early in the epic to confirm that dynamic-field-renderer exposes a field metadata interface including field type and sensitivity flags. Add a dictation_eligible flag to the field schema that the renderer passes to DictationMicrophoneButton as a constructor parameter.

Contingency: If the renderer cannot be modified without breaking changes, implement dictation eligibility as a separate lookup against org-field-config-loader using the field key as the lookup identifier, bypassing the renderer integration and keeping the dictation components fully decoupled from the report form architecture.