critical priority low complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

ios/Runner/Info.plist contains NSMicrophoneUsageDescription with the rationale string in both Norwegian Bokmål ('Appen trenger tilgang til mikrofon for å la deg snakke inn rapporttekst.') and English ('The app needs microphone access to let you dictate report text.'). The key is present in the localised Localizable.strings file for both nb and en locales.
android/app/src/main/AndroidManifest.xml contains <uses-permission android:name='android.permission.RECORD_AUDIO' /> at the manifest level.
On iOS 17+ and Android 13+, the first time the speech-to-text overlay is activated, the OS permission dialog is shown before any recording begins — no crash or silent denial occurs.
If the user denies the microphone permission, the speech-to-text overlay shows an in-app message explaining why the permission is needed and offering a link to open Settings, without crashing or leaving the field in an unusable state.
If the user has previously denied the permission permanently, the overlay detects the permanentlyDenied state and opens the app's Settings page via openAppSettings(), with a plain-language explanation.
On iOS, the permission_handler package correctly maps NSMicrophoneUsageDescription; building the iOS app with 'flutter build ios' produces no permission-related warnings.
On Android, a clean install triggers the RECORD_AUDIO runtime dialog on first use; the permission is correctly reflected in the device's permission manager.
A manual QA checklist is documented covering: first-use grant, first-use deny, permanent deny + settings redirect, and re-grant after settings change.

Technical Requirements

frameworks
Flutter
speech_to_text Flutter Package (iOS SFSpeechRecognizer / Android SpeechRecognizer)
apis
permission_handler Flutter package — for runtime permission checking and settings redirect
speech_to_text Flutter Package
performance requirements
Permission check must be non-blocking — use async/await and show a loading indicator if the OS dialog takes >500ms to appear
security requirements
Audio must never be captured before the user grants the microphone permission — guard all SpeechToText.listen() calls behind a confirmed PermissionStatus.granted check
DictationScopeGuard must be implemented to ensure microphone cannot be activated during sensitive peer conversations (requirement from Blindeforbundet workshop)
Audio is never uploaded to third-party servers — only on-device SFSpeechRecognizer/SpeechRecognizer is used; verify this in the speech_to_text package configuration
Microphone permission rationale must be honest and specific — do not use generic strings that could mislead the user about audio usage scope
ui components
PermissionDeniedBanner — in-app banner shown when microphone permission is denied
PermissionSettingsRedirectDialog — dialog with 'Open Settings' CTA for permanently denied state

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the permission_handler package (^11.x) for cross-platform permission checking — do not call speech_to_text's initialize() before checking and receiving PermissionStatus.granted. Add NSMicrophoneUsageDescription to both Runner/Info.plist (for the main target) and any RunnerTests/Info.plist if tests require microphone. For Android, RECORD_AUDIO is a dangerous permission requiring a runtime dialog on API 23+; ensure it is in the main block, not inside . The DictationScopeGuard should be a BLoC event or Riverpod provider that tracks whether a peer session is currently 'in progress' — if in-progress, disable the microphone button entirely at the widget level and announce 'Recording unavailable during active session' via Semantics.

Localised permission strings go in ios/Runner/nb.lproj/InfoPlist.strings and ios/Runner/en.lproj/InfoPlist.strings — the key is NSMicrophoneUsageDescription in both files.

Testing Requirements

Manual QA on physical devices (not simulator): iOS device — grant permission on first use; deny on first use; deny permanently and verify Settings redirect; re-grant from Settings and verify recording resumes. Android device — same four scenarios on Android 13+. Automated widget tests: SpeechToTextOverlay shows PermissionDeniedBanner when mock permission status is denied; shows PermissionSettingsRedirectDialog when status is permanentlyDenied; calls SpeechToText.listen() only after confirmed PermissionStatus.granted. Build verification: 'flutter build ios --no-codesign' completes without NSMicrophoneUsageDescription warnings; 'flutter build apk' includes RECORD_AUDIO in merged manifest.

Component
Post-Session Report Screen
ui high
Epic Risks (2)
medium impact high prob technical

End-to-end integration tests that span Flutter UI → Supabase → RLS → storage are inherently flaky in CI due to network timing, test database state, and Supabase cold-start latency. Flaky tests erode confidence and slow the release pipeline.

Mitigation & Contingency

Mitigation: Use a dedicated Supabase test project with seeded org and user fixtures. Wrap all E2E tests in retry logic with a fixed seed and tear-down hooks. Keep E2E tests in a separate test suite that runs on-demand rather than on every PR, with unit and widget tests as the primary CI gate.

Contingency: If E2E tests remain unreliable, replace the Supabase calls in integration tests with a verified fake (in-memory repository implementations) and promote the real Supabase tests to a nightly scheduled run rather than blocking PR merges.

high impact medium prob security

Health status, course interest, and assistive device fields contain personal health data. If any logger, analytics event, or crash reporter captures field values — through automated error serialisation or developer-added debug logs — the feature could violate GDPR and Blindeforbundet's data processor agreement.

Mitigation & Contingency

Mitigation: Audit all log statements in the report feature's code paths before the epic is marked done. Apply a PII-safe logging wrapper that strips field values from any serialised form state before it reaches the logger. Add a CI lint rule that flags direct logger calls within report-related files.

Contingency: If PII is found in logs post-launch, immediately disable the affected logging call and rotate any credentials that were exposed. Notify the data protection officer and document the incident per GDPR Article 33 requirements.