high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

When TranscriptionStateManager transitions to 'cancelled' state, the TextEditingController is restored to its exact pre-dictation value (text and cursor position)
No partial transcription text appears in the TextField after cancellation — the field content is bit-for-bit identical to the pre-dictation snapshot
The preview area is hidden synchronously with cancellation — no animation delay before hiding
The undo history stack is unchanged after cancellation — pre-dictation undo steps remain available, and the cancelled dictation does not introduce any new undo entry
If the user manually edits the field while partial results are showing (edge case), and then cancels, the field restores to the state at the moment dictation started (not the manually-edited state during dictation)
Cancellation works correctly when triggered by: user pressing cancel button, app going to background, system interruption (call, alarm), and timeout
Pre-dictation snapshot is captured exactly once at the moment the state transitions from 'hidden/idle' to 'recording' — not on every partial result
Memory: the pre-dictation snapshot is cleared (set to null) after merge or cancel to avoid holding stale references
Widget test covers cancel-with-empty-field, cancel-with-existing-text, cancel-after-multiple-partial-results, and cancel-after-user-edit-during-partial

Technical Requirements

frameworks
Flutter
Riverpod
apis
TranscriptionStateManager Riverpod provider
TextEditingController
AppLifecycleListener (Flutter 3.13+) for background cancellation
performance requirements
Preview area dismissal on cancel must be synchronous (no fade-out animation) — instant clear to avoid impression of committed text
Restoration of TextEditingController completes within one frame
security requirements
Pre-dictation snapshot stored only in widget memory — never written to disk or sent over network
Partial transcription content discarded from memory immediately on cancel — no retention
ui components
TranscriptionPreviewField (component 658)
TextEditingController

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Capture the pre-dictation snapshot in a `ref.listen` callback that fires when the state transitions to 'recording': `_preDictationSnapshot = controller.value;`. On 'cancelled' state: `controller.value = _preDictationSnapshot!; _preDictationSnapshot = null;`. Do NOT use `controller.text =` — use `controller.value =` to also restore cursor position and selection. For immediate preview dismissal without animation: instead of relying on AnimatedSize to animate out, reset the partial text state synchronously so AnimatedSize has zero target height immediately.

Guard against null snapshot (edge case: cancel fires before recording started) with a null check — if null, no-op. For app-background-triggered cancellation, listen to `AppLifecycleListener.onInactive` and dispatch a cancel event to TranscriptionStateManager rather than handling it in the widget — this keeps cancellation logic in the state machine, not scattered across widgets.

Testing Requirements

Unit tests: Test the snapshot-and-restore mechanism: given a pre-dictation TextEditingValue, simulate partial results updating the preview, then trigger cancel and assert controller.value equals the snapshot. Test that snapshot is null after cancel. Test undo stack not modified: use UndoHistoryController or mock to assert no new undo entry was created by the cancel operation. Widget tests: Full end-to-end widget test — set initial field content, start fake dictation session via mock provider, emit partial results, trigger cancel state, assert field content unchanged.

Test all cancellation trigger scenarios using provider overrides. Manual tests: Run the full dictation-and-cancel flow on device. Verify field content is pixel-perfect identical to before dictation. Verify undo (shake/Ctrl+Z) still works for pre-dictation edits after cancel.

Component
Transcription Preview Field
ui medium
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.