critical priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

Each step widget receives the Cubit via BlocProvider.of or context.read — no direct instantiation inside step widgets
ActivityTypeStep dispatches ActivityTypeSelected(activityTypeId) when user taps an activity type chip; the Cubit state updates and BlocBuilder rebuilds the widget with the new selection highlighted
DateStep dispatches DateChanged(DateTime) when the user picks a date; the displayed date in the step matches the cubit state value
DurationStep dispatches DurationChanged(int) when the slider or increment/decrement control changes; displayed value reflects state
NotesStep dispatches NotesChanged(String) on every text change (debounced 300ms to avoid excessive rebuilds); text field value is driven by state, not local controller state
All steps rebuild only when the fields they care about change — use BlocBuilder with buildWhen to prevent unnecessary rebuilds of unrelated steps
The 'Next' button in each step is enabled only when that step's validation flag (canProceed for current step) is true in Cubit state
No step holds local ephemeral state that duplicates Cubit state — all truth is in the Cubit
Integration smoke test: selecting activity type in step 1, advancing to step 4 (notes), pressing back to step 1 shows previously selected activity type still selected
Submitting the form dispatches SubmitActivityRegistration event; the Cubit transitions to ActivityRegistrationSubmitting state and all step controls become non-interactive

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
BlocBuilder
BlocListener
data models
ActivityRegistration
ActivityType
performance requirements
BlocBuilder buildWhen must filter irrelevant state changes to prevent full step rebuilds
NotesStep text debounce of 300ms to avoid excessive event dispatching
security requirements
Do not log state contents containing user-entered notes to console in production builds
ui components
ActivityTypeStep
DateStep
DurationStep
NotesStep
ConfirmationStep

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use BlocBuilder with a tight buildWhen predicate per step — e.g., DateStep only rebuilds when date changes. For the NotesStep TextEditingController, initialize from state in initState and sync via a BlocListener that calls controller.text = newNotes only when state changes externally (e.g., pre-fill injection) — not on every keystroke, to avoid cursor jumping. Dispatch events using context.read().add(...) inside callbacks, not inside build. Use BlocConsumer (not BlocBuilder) in steps that need to react to side effects like error toasts.

Testing Requirements

Widget tests (flutter_test) for each step in isolation using MockCubit (mocktail or bloc_test MockBloc). Test: correct widget tree rendered for given state, correct event dispatched on user interaction (tap, drag, type), Next button enabled/disabled based on validation state. Integration test: pump full wizard inside BlocProvider, simulate user completing all steps, verify final Cubit state contains all entered values and submission event fires. Use WidgetTester.pump() after each interaction.

Component
Activity Type Selection Step
ui low
Dependencies (5)
Build the optional NotesStep widget with a multi-line text field and an OS speech-to-text microphone button. The field must be clearly marked optional, support dictation via the platform's native speech API, and not record audio during the peer-mentor session — only during post-session reporting. epic-quick-activity-registration-wizard-ui-task-005 Build the ActivityTypeStep widget displaying selectable activity-type chips. The last-used activity type must be pre-selected on mount. Chips must have 48dp minimum touch targets, WCAG AA contrast, and correct semantics (role=radio) for screen readers. epic-quick-activity-registration-wizard-ui-task-002 Build the DateStep widget with today's date pre-filled, recent-date quick-select chips (yesterday, 2 days ago), and a calendar override picker. Ensure date formatting is locale-aware and the calendar picker is fully accessible with keyboard/switch navigation. epic-quick-activity-registration-wizard-ui-task-003 Build the DurationStep widget with duration preset chips (15 min, 30 min, 45 min, 60 min, 90 min, 120 min) defaulting to 30 minutes, plus a free-entry numeric input fallback. Chips must auto-scroll to the selected value and provide haptic feedback on selection. epic-quick-activity-registration-wizard-ui-task-004 Define the Cubit state class covering all wizard fields (activityTypeId, date, durationMinutes, notes), loading/success/error states, and validation flags. Include copyWith, equality, and serialization. This model drives all five step widgets via BlocBuilder. epic-quick-activity-registration-wizard-ui-task-006
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.