high priority medium complexity integration pending backend specialist Tier 3

Acceptance Criteria

PartialTranscriptionRepository is injected into TranscriptionStateManager via the constructor; the provider wires the dependency via ref.watch(partialTranscriptionRepositoryProvider)
On construction (or on first listen), TranscriptionStateManager calls await _partialRepo.findByFieldId(fieldId); if a record is found, state is immediately set to DictationProcessing(partialTranscript: record.text) without requiring startDictation() to be called
The UI receiving DictationProcessing on first load can display a recovery banner/prompt to the user (state carries enough payload for the UI to act)
During DictationRecording, every PartialResult event triggers a debounced (≥300ms) call to _partialRepo.upsert(fieldId, interimTranscript) — not a direct call on every event to avoid I/O flood
On successful DictationComplete, TranscriptionStateManager calls _partialRepo.deleteByFieldId(fieldId) to clear the recovery record; this call is awaited before or after state transition (not fire-and-forget — failure must be logged)
On cancelDictation(), the partial record is also deleted from the repository
If the repository throws during upsert, the error is logged at warning level but does not affect the state machine — dictation continues
If the repository throws during the initial load check, the error is caught, logged, and state defaults to DictationIdle (safe degradation)
All repository interactions are async and non-blocking relative to the state machine's synchronous transitions
flutter analyze produces zero warnings on all changed files

Technical Requirements

frameworks
Flutter
Riverpod (flutter_riverpod)
apis
PartialTranscriptionRepository internal API (component 662)
flutter_secure_storage or equivalent secure local persistence
performance requirements
Upsert calls during DictationRecording must be debounced (min 300ms) to cap I/O at ~3 writes/second regardless of recognition engine event frequency
Initial recovery check (findByFieldId) must complete within 500ms; if it takes longer, emit DictationIdle and proceed — do not block the UI on a slow storage read
Delete on complete must not block the DictationComplete state emission — call it after state transition if needed
security requirements
Partial transcript data stored locally must use flutter_secure_storage (iOS Keychain / Android Keystore) — never plain SharedPreferences, as transcripts may contain sensitive peer interaction content
Repository must scope storage keys by (organizationId, userId, fieldId) to prevent cross-user data leakage on shared devices
On app uninstall, flutter_secure_storage data is cleared by the OS — document this as the expected behaviour, no manual cleanup needed

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Implement debounce using a Timer? _persistDebounce field: cancel and reset the timer on each PartialResult, fire the upsert only when the timer fires. Choose 300ms as the debounce interval (configurable via a const). Place the initial recovery check in an async init method called from the StateNotifier constructor body using Future.microtask(() async { ...

}) so it does not block provider instantiation. The abstract interface for PartialTranscriptionRepository (already hinted at in task-004) must have at minimum: Future findByFieldId(String fieldId), Future upsert(String fieldId, String text), Future deleteByFieldId(String fieldId). Ensure the DictationProcessing state (already defined in task-001) carries a partialTranscript field so the UI can display the recovered text and prompt the user to review or discard. The recovery flow (DictationProcessing on load) should be clearly distinguished from the normal DictationProcessing state (after stopDictation) — consider adding an isRecovery: bool flag to DictationProcessing if the distinction matters for UI treatment.

Testing Requirements

Unit tests using flutter_test with a fake PartialTranscriptionRepository (in-memory implementation for tests). Test cases: (1) on init with existing partial record → state is DictationProcessing with correct transcript text; (2) on init with no partial record → state is DictationIdle; (3) on init with repository error → state is DictationIdle (safe degradation); (4) PartialResult event triggers debounced upsert — verify upsert called after debounce interval using fake async; (5) DictationComplete → deleteByFieldId called; (6) cancelDictation() → deleteByFieldId called; (7) upsert error does not change state machine state; (8) concurrent partial emissions within debounce window result in exactly one upsert. Use fake_async package for debounce testing. Minimum 90% coverage on repository integration code paths.

Component
Transcription State Manager
service medium
Epic Risks (2)
medium impact low prob technical

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.

medium impact medium prob scope

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.