critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

initialize() checks microphone permission via permission_handler before any engine negotiation
If permission is PermissionStatus.granted, initialization proceeds to engine availability check via NativeSpeechApiBridge
If permission is PermissionStatus.denied, initialize() requests permission; on continued denial it emits SpeechError(code: permissionDenied)
If permission is PermissionStatus.permanentlyDenied, initialize() emits SpeechError(code: permissionPermanentlyDenied) and does NOT request permission again
If NativeSpeechApiBridge reports engine unavailable, initialize() emits SpeechError(code: engineUnavailable)
On successful initialization, SpeechStatusChange(status: SpeechStatus.idle) is emitted
Calling startListening() before initialize() completes throws a StateError with a clear message
initialize() is idempotent: calling it when already initialized is a no-op (no duplicate permission requests)
All permission and engine checks are injectable/mockable for unit testing — no static method calls to permission_handler

Technical Requirements

frameworks
Flutter
Riverpod
apis
permission_handler
NativeSpeechApiBridge
data models
SpeechRecognitionEvent
SpeechErrorCode
SpeechStatus
performance requirements
initialize() must complete or emit an error within 5 seconds on a real device
Permission dialog must not be shown more than once per app session for the same permission
security requirements
Microphone permission must be explicitly granted before any audio capture begins — never assume granted
In Info.plist (iOS) and AndroidManifest.xml (Android), NSMicrophoneUsageDescription and RECORD_AUDIO must be declared with a Norwegian-language justification string relevant to the app's dictation use case

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Inject permission_handler as a PermissionHandlerAdapter interface rather than calling Permission.microphone.status directly — this makes the class unit-testable. The NativeSpeechApiBridge is already injectable via Riverpod from the foundation layer. Track initialization state with a private _isInitialized bool and a Completer _initCompleter to support awaiting initialization in startListening(). Use a try/catch around the entire initialize() body and emit a SpeechError for any unexpected exceptions to prevent silent failures.

The iOS permission string (NSMicrophoneUsageDescription) should read: 'Appen bruker mikrofonen til å skrive inn tekst via tale.' (Norwegian). This is critical for App Store review compliance.

Testing Requirements

Unit tests must mock the permission_handler and NativeSpeechApiBridge dependencies. Test scenarios: (1) permission already granted + engine available → idle status emitted, (2) permission denied on first request → error emitted, (3) permission permanently denied → error emitted without re-requesting, (4) engine unavailable → engine error emitted, (5) double initialize() call → no side effects on second call. Use flutter_test. No physical device needed.

Integration test on real device should verify the permission dialog appears on first launch and that subsequent launches skip the dialog.

Component
Speech Recognition Service
service high
Epic Risks (2)
medium impact medium prob technical

The speech_to_text Flutter package delegates accuracy entirely to the OS-native engine. Norwegian accuracy for domain-specific vocabulary (medical terms, organisation names, accessibility terminology) may fall below the 85% acceptance threshold on older devices or in noisy environments, causing user frustration and manual correction overhead that negates the time saving.

Mitigation & Contingency

Mitigation: Configure the SpeechRecognitionService with Norwegian as the explicit locale and test against a representative corpus of peer mentoring vocabulary on target devices. Expose locale switching so users can fallback to Bokmål vs Nynorsk. Clearly set user expectations in the UI that transcription is a starting point for editing, not a finished product.

Contingency: If accuracy is consistently below threshold on specific device/OS combinations, add a device-capability check that hides the dictation button with an explanatory message rather than offering a degraded experience. Document affected device models for QA and org contacts.

medium impact low prob dependency

The speech_to_text Flutter package is a third-party dependency that may introduce breaking API changes or deprecations on major version upgrades, requiring rework of SpeechRecognitionService when Flutter or platform OS versions are updated.

Mitigation & Contingency

Mitigation: Wrap all speech_to_text API calls behind the SpeechRecognitionService interface so that package changes are isolated to one file. Pin the package version in pubspec.yaml and review changelogs before any upgrade. Write integration tests that exercise the package contract so regressions are caught immediately.

Contingency: If the package is abandoned or has unresolvable issues, NativeSpeechApiBridge already provides the platform-channel abstraction needed to implement a direct plugin replacement with minimal changes to SpeechRecognitionService.