critical priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Widget renders an inline microphone icon button within a free-text TextField without overflowing or misaligning the field layout
Idle state: renders mic_none or equivalent outlined microphone icon in secondary/tertiary color from design tokens
Active/recording state: renders filled microphone icon using the primary brand color design token
Processing state: renders a loading indicator (CircularProgressIndicator or equivalent) replacing the microphone icon
Tapping the button in idle state dispatches a startDictation intent to TranscriptionStateManager for the correct fieldId
Tapping the button in active state dispatches a stopDictation intent
Button touch target is at minimum 44×44 logical pixels (WCAG 2.2 AA target size criterion 2.5.8)
Semantics widget wraps the button with a descriptive label: 'Start dictation' when idle, 'Stop dictation' when recording, 'Processing speech' when processing
Widget is stateless beyond its Riverpod watch — no local StatefulWidget required
Widget accepts a required `fieldId` parameter used to key the correct family provider instance
Widget uses only design token constants (no hardcoded hex colors, font sizes, or spacing values)

Technical Requirements

frameworks
Flutter
Riverpod
apis
TranscriptionStateManager (via Riverpod provider family)
DictationScopeGuard (read for availability)
data models
TranscriptionState
TranscriptionStatus
performance requirements
Widget rebuild triggered only by relevant TranscriptionState changes — use select() to avoid full-state rebuilds
Icon swap must not cause layout shift in the parent TextField
security requirements
Button must be visually and functionally disabled (not just hidden) when permissions are denied to prevent accidental permission re-requests
ui components
Semantics wrapper
IconButton with minimum 44×44 touch target
CircularProgressIndicator (processing state)
AnimatedSwitcher for smooth icon transitions

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use `Consumer` or `ConsumerWidget` with `ref.watch(transcriptionProvider(fieldId).select((s) => s.status))` to minimize rebuilds. Wrap the IconButton in a `SizedBox(width: 44, height: 44)` to guarantee minimum touch target even if the icon itself is smaller. Use `AnimatedSwitcher` with a fade transition for smooth icon state changes — this also helps users with cognitive disabilities by providing a clear visual change cue. Semantic labels should be pulled from the app's localization system (even if that means adding 3 new ARB keys) — do not hardcode English strings.

For the design token usage, import from `AppColors`, `AppSpacing`, and `AppSizing` constants — match the pattern used by existing `AppButton` and `AppTextField` widgets in the shared component library.

Testing Requirements

Widget tests (flutter_test): (1) Pump widget with provider override in idle state — verify mic_none icon is rendered and Semantics label is 'Start dictation'. (2) Override provider with recording state — verify filled mic icon and 'Stop dictation' label. (3) Override provider with processing state — verify CircularProgressIndicator is visible and button is non-interactive. (4) Tap in idle state — verify startDictation() was called on mock state manager.

(5) Tap in recording state — verify stopDictation() was called. (6) Verify touch target is at least 44×44 using tester.getSize(). (7) Golden test: render all three icon states side-by-side and compare to approved screenshot.

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.