Implement TranscriptionStateManager Cubit core
epic-speech-to-text-input-state-management-task-002 — Implement the TranscriptionStateManager as a Riverpod StateNotifier (or Cubit wrapped in StateNotifierProvider.family keyed by field ID string). The notifier must own the full state machine, transition between states in response to domain events from SpeechRecognitionService, and expose the current DictationState and latest partial/final transcript as observable streams. Include the post-activity-only constraint check at the state machine level as a secondary guard after DictationScopeGuard validation.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Place the file at lib/features/dictation/state/transcription_state_manager.dart with its provider at lib/features/dictation/providers/transcription_state_manager_provider.dart. Use StateNotifier
Implement the state machine as a private _transition(DictationState newState) method that validates the transition is legal before calling state = newState, logging invalid transitions at debug level. The partialTranscriptStream can be implemented as a StreamController
Testing Requirements
Unit tests using flutter_test and a mock/fake SpeechRecognitionService. Test cases required: (1) initial state is DictationIdle; (2) startDictation() in idle transitions to DictationRequestingPermission; (3) startDictation() in non-idle states is no-op; (4) cancelDictation() returns to DictationIdle from DictationRecording; (5) scope violation guard transitions to DictationError with scopeViolation code; (6) stopDictation() in recording state transitions to DictationProcessing; (7) partialTranscriptStream emits when DictationRecording.interimTranscript updates; (8) dispose cancels the service subscription. Use Riverpod's ProviderContainer for isolated testing. Minimum 85% line coverage on TranscriptionStateManager.
If a peer mentor rapidly switches between dictation-enabled fields while a session is still processing, the Riverpod family provider may share state or the SpeechRecognitionService may receive conflicting start/stop commands, causing orphaned recording sessions or state corruption.
Mitigation & Contingency
Mitigation: Design the state manager to enforce a single active dictation session globally via a shared active-field-key notifier. When a new field requests dictation while another session is active, automatically issue a stop command to the existing session and await completion before starting the new one. Test this concurrency scenario explicitly.
Contingency: If concurrency issues persist in integration testing, add a global dictation mutex at the SpeechRecognitionService level that serialises all start requests, with a short debounce to handle rapid field switching.
The recovery flow for interrupted dictation sessions (app restart with persisted partials) has ambiguous UX requirements. If recovery is automatic and the user has already typed different content into the field, the merge logic may overwrite or duplicate user input, causing data loss.
Mitigation & Contingency
Mitigation: Define the recovery behaviour explicitly: recovery should surface as an opt-in prompt ('You have unsaved dictation — insert or discard?') rather than an automatic merge. The state manager emits a dedicated recoverable-partial state that the UI layer renders as a non-blocking action sheet.
Contingency: If the recovery UI adds too much complexity for the initial release, scope recovery to display only the partial text in the preview field on re-open without auto-insertion, letting the user manually copy if needed, and defer the automatic recovery prompt to a subsequent iteration.