high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

RecordingStateIndicator contains zero local state variables (no setState, no local StateProvider) — all state is sourced from TranscriptionStateManager
When TranscriptionStateManager emits 'hidden', the indicator overlay fades out over 200ms and is removed from the widget tree after the animation completes
When TranscriptionStateManager emits 'recording', the indicator overlay fades in over 200ms and displays the recording visual (animated microphone icon or waveform)
When TranscriptionStateManager emits 'processing', the indicator transitions to a spinner/loading visual with a 200ms cross-fade
When TranscriptionStateManager emits 'error', the indicator displays the error message text from the state and uses the error visual style (red tint or error icon per design tokens)
Error message displayed is always the last non-null error from the state — persists until next state transition
Fade transitions are driven by AnimatedSwitcher or AnimatedOpacity — not by manual opacity tweening with timers
No visual jank on state transitions when tested on a low-end Android device (Flutter profile mode)
Widget disposes cleanly — no stream subscriptions or animation controllers leaked on widget removal
Widget test asserts that changing the mocked TranscriptionStateManager state updates the rendered widget tree correctly for all four states

Technical Requirements

frameworks
Flutter
Riverpod
apis
TranscriptionStateManager Riverpod provider
Flutter AnimatedSwitcher / AnimatedOpacity
performance requirements
Fade transitions complete in ≤200ms
No dropped frames (jank) during transition on mid-range hardware — verify with Flutter DevTools timeline
security requirements
Error messages displayed in the indicator must not contain any PII or partial transcription content from sensitive conversations
ui components
RecordingStateIndicator (component 657)
AnimatedSwitcher (Flutter built-in)
Design token colors for error, active, and neutral states

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Convert RecordingStateIndicator to a ConsumerWidget (Riverpod). Use `ref.watch(transcriptionStateManagerProvider)` to read the current state reactively. Use AnimatedSwitcher with a FadeTransition child for the state-dependent content — key each child by the state type so Flutter knows to animate between them. Persist the last error message by extracting it from the state in the build method: `final errorMsg = state is TranscriptionError ?

state.message : _lastError;` — store `_lastError` as a private field updated only when a new error arrives. Avoid AnimationController unless custom curves are required — prefer implicit animations for this complexity level. Ensure the overlay wrapper uses IgnorePointer when in hidden state so it does not consume touch events.

Testing Requirements

Widget tests: Provide a fake/override TranscriptionStateManager via Riverpod ProviderContainer overrides. Drive the provider through all four states (hidden, recording, processing, error) and assert the rendered widget tree matches expected output for each. Assert error message text is visible in error state. Assert overlay widget is absent from the tree in hidden state.

Unit tests: Test the state-to-visual mapping logic in isolation if extracted to a pure function. Manual/visual tests: Run on simulator with slow animations enabled (timeDilation = 5.0) and verify no layout jumps or opacity flicker during transitions. Regression test: confirm task-004 accessibility announcements still fire correctly after this refactor (integration between tasks).

Component
Recording State Indicator
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.