medium priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Widget renders a multi-line TextField with a label 'Notes (optional)' and placeholder text indicating the field is not required
A microphone icon button is displayed adjacent to the text field and activates speech-to-text on tap
When microphone is active, a visual recording indicator (e.g., animated mic icon or pulsing border) is shown and a Semantics live region announces 'Microphone active — speak now' to screen readers
When recording stops, the transcribed text is appended (not replaced) to any existing field content
The microphone button is disabled and shows a tooltip 'Speech-to-text not available' if the device does not support speech recognition (check SpeechToText.initialize() result)
A permission prompt is shown if microphone permission has not been granted; the app does not crash if the user denies permission
The widget includes a visible note: 'Only use after the session — not during conversations with contacts' (per the DictationScopeGuard requirement from the security context)
Submitting the wizard step with an empty notes field is valid — the field is optional
Maximum input length is 2000 characters; a character counter is shown near the field limit
Widget test verifies: optional label, recording state toggle, permission denial graceful handling, and character counter

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
speech_to_text
apis
speech_to_text Flutter Package (iOS SFSpeechRecognizer / Android SpeechRecognizer)
data models
activity
performance requirements
Speech recognition initialization completes within 2 seconds on supported devices
Transcribed text appended to field within 500ms of recognition result callback
security requirements
Audio must never be recorded during an active peer-mentor session — the DictationScopeGuard must be checked before enabling the mic button; if a session is active, the button is disabled with a clear message
On-device recognition preferred (SpeechToText with onDevice: true) to avoid sending audio to third-party servers
Microphone permission must be requested with a clear usage description explaining post-session note dictation
Screen reader must announce when recording starts and stops (GDPR + accessibility requirement from workshop notes)
ui components
MultiLineNotesField (TextField, maxLines: 6, maxLength: 2000)
MicrophoneButton (IconButton with recording state animation)
RecordingIndicator (animated visual + Semantics live region)
PostSessionWarningBanner (static text notice)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the speech_to_text package (^6.x). Initialize in the widget's initState (or a Riverpod AsyncNotifier) and store the SpeechToText instance. For DictationScopeGuard, read a Riverpod provider that exposes whether a peer session is currently active; if true, disable the mic button. For appending transcription, listen to the SpeechToText.listen() onResult callback and append result.recognizedWords to the TextEditingController's current text.

Use onDevice: true in the listen() call as the first preference, falling back to cloud if unsupported. Wrap all SpeechToText calls in try/catch and show a SnackBar on unexpected errors. The PostSessionWarningBanner should use a distinct background color from design tokens (e.g., warning/amber) to be visually prominent without being alarming. Keep microphone state (isListening) in a local ValueNotifier or StateProvider — this is ephemeral UI state, not wizard business state.

Testing Requirements

Widget tests using flutter_test with a mocked SpeechToText. Test 1: widget renders with optional label and placeholder. Test 2: tapping mic button when SpeechToText is available starts listening and shows recording indicator. Test 3: tapping mic again stops listening and appends transcribed text to the field.

Test 4: when SpeechToText.initialize() returns false, mic button is disabled and shows tooltip. Test 5: when permission is denied (mock PermissionStatus.denied), widget shows graceful error message without crashing. Test 6: entering 2001 characters is prevented or capped at 2000. Test 7: Semantics live region is present in the widget tree when recording is active.

Test 8: PostSessionWarningBanner is always visible regardless of recording state.

Component
Optional Notes Input Step
ui low
Epic Risks (4)
high impact medium prob scope

As wizard steps accumulate additional features (duplicate warning, retroactive date chips, custom duration entry), the two-tap happy path may inadvertently require extra interactions. A step that previously auto-advanced may start requiring a confirmation tap, breaking the core promise of the feature and increasing friction for high-frequency users like HLF's 380-registration peer mentor.

Mitigation & Contingency

Mitigation: Define and automate a regression test that performs the complete two-tap happy path (open bottom sheet → confirm → confirm) and asserts the confirmation view is reached in exactly two tap events. Run this test in CI on every PR touching the wizard. Treat any failure as a blocking defect.

Contingency: If a new feature unavoidably adds a tap to the happy path, provide a 'quick mode' toggle in user settings that collapses the wizard to a single-confirmation screen for users who never change defaults.

medium impact medium prob technical

Flutter bottom sheets are dismissed on back-button press or background tap by default. If the wizard state is not preserved, a peer mentor who accidentally dismisses mid-flow loses all their entered data and must start over — a significant frustration for users with cognitive disabilities or motor impairments who take longer to fill each step.

Mitigation & Contingency

Mitigation: Implement the wizard state as a persistent Cubit that outlives the bottom sheet widget's lifecycle, scoped to the registration feature route. On re-open, the Cubit restores the previous step and field values. Add a 'discard changes?' confirmation dialog when the user explicitly dismisses a partially filled wizard.

Contingency: If persistent state proves difficult to implement with the chosen routing strategy, implement draft auto-save to a local draft repository every time a field value changes, and restore from draft on the next open.

high impact high prob technical

Multi-step wizard bottom sheets are among the most complex accessibility scenarios in Flutter. Screen readers (TalkBack, VoiceOver) may not announce step transitions, focus may land on the wrong element after advancing, and animated transitions can interfere with the accessibility tree update cycle — making the feature unusable for Blindeforbundet users who rely on screen readers.

Mitigation & Contingency

Mitigation: Assign each wizard step a unique Semantics container with a live region announcement on mount. Use ExcludeSemantics on inactive steps during transition animations. Test each step transition manually with TalkBack and VoiceOver as part of the definition of done for each step component.

Contingency: If animated transitions cause accessibility tree corruption, disable step transition animations entirely in accessibility mode (detected via MediaQuery.accessibleNavigation) and use instant step replacement instead.

medium impact medium prob dependency

The NotesStep relies on the OS keyboard's built-in dictation button for speech-to-text input. This button's availability, position, and behaviour varies significantly between iOS (reliable, visible dictation key) and Android (varies by keyboard, OEM skin, and language settings). HLF and Blindeforbundet specifically requested this capability; if it is unreliable on Android, it fails a SHOULD HAVE requirement for a significant portion of users.

Mitigation & Contingency

Mitigation: Document that the notes dictation feature depends on the device's native keyboard dictation and requires no in-app microphone permission. Add explicit placeholder copy informing users they can use their keyboard's dictation button. Test on a minimum of three Android OEM keyboards (Gboard, Samsung, Swiftkey) and two iOS versions.

Contingency: If native keyboard dictation is too unreliable on Android, implement a fallback in-app microphone button in the NotesStep that triggers the platform's SpeechRecognition API directly via a method channel, scoped only to the notes field with no session recording capability.