high priority low complexity backend pending backend specialist Tier 4

Acceptance Criteria

StateNotifierProvider.family<TranscriptionStateManager, TranscriptionState, String> is declared and accessible from any widget tree location beneath the root ProviderScope
Two concurrent providers keyed by distinct fieldIds (e.g., 'report-summary' and 'contact-notes') maintain completely independent TranscriptionState values — changing one does not affect the other
Disposing a provider instance (e.g., via AutoDispose or explicit invalidation) cancels any active SpeechRecognitionService stream subscription within 200ms
In-flight partial transcript is persisted to PartialTranscriptionRepository before disposal completes — verified by reading repository after disposal
Provider overrides are registered in the root ProviderScope and work correctly in both test and production environments
A dedicated provider file (e.g., transcription_providers.dart) documents the family pattern with inline comments explaining the fieldId keying strategy
No provider leak warnings appear in the Riverpod inspector during a full lifecycle test (create → use → dispose)
Provider family is exported via the component's public API barrel file

Technical Requirements

frameworks
Flutter
Riverpod
apis
SpeechRecognitionService stream API
PartialTranscriptionRepository persistence API
data models
TranscriptionState
PartialTranscript
DictationFieldId
performance requirements
Provider instantiation must complete in under 50ms
Disposal sequence (cancel subscription + persist partial) must complete within 200ms to avoid jank on screen transitions
No memory leaks: AutoDispose variant must be used to ensure providers are GC'd when no longer watched
security requirements
PartialTranscriptionRepository must persist data using encrypted local storage — partial transcripts may contain sensitive medical or personal information
FieldId values must not be derived from PII (use opaque UUID or widget key hash)
Provider state must not be accessible from outside the ProviderScope subtree

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Use `final transcriptionProvider = StateNotifierProvider.autoDispose.family((ref, fieldId) { ... })`. AutoDispose is critical here — without it, providers for fields that have scrolled off-screen accumulate indefinitely. In the dispose body of TranscriptionStateManager, call `_speechService.cancel()` synchronously and `_partialRepo.persist(fieldId, state.partialTranscript)` as an unawaited Future (or await it in an override for tests).

Register `overrideWithValue` in ProviderScope for both SpeechRecognitionService and PartialTranscriptionRepository during widget and integration tests. Avoid placing the family declaration in the same file as TranscriptionStateManager to prevent circular imports — create a dedicated `transcription_providers.dart` in the component directory.

Testing Requirements

Unit tests (flutter_test + mocktail): (1) Verify two family instances with different fieldIds hold independent state after concurrent mutations. (2) Verify dispose triggers subscription cancellation on the mock SpeechRecognitionService. (3) Verify dispose calls PartialTranscriptionRepository.persist() with the correct in-flight transcript before stream is closed. (4) Verify ProviderScope.overrides correctly substitutes mock implementations for SpeechRecognitionService and PartialTranscriptionRepository.

Widget test: Mount a simple widget that watches two family providers simultaneously and confirm independent rebuild cycles. All tests must pass with zero Riverpod ProviderException warnings in output.

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.