high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

The bottom sheet opens immediately with default values (date = today, duration = 30 min, no activity type) — there is no loading spinner blocking the sheet from appearing
Within 200ms of sheet open, if a previous registration exists in the preferences store, ActivityTypeStep shows the last-used activity type pre-selected and DurationStep shows the last-used duration
The pre-fill is dispatched as a PreFillDefaults(activityTypeId, durationMinutes) event to the Cubit — not via direct state mutation
If the preferences store read fails or returns null, the wizard opens with default values silently — no error is shown to the user
Pre-fill does not override any field the user has already interacted with — if the user selects an activity type before pre-fill resolves, the pre-fill is discarded
After successful registration, the completed activityTypeId and durationMinutes are written back to the preferences store so the next session pre-fills correctly
Pre-fill respects the activity type list — if the last-used activityTypeId no longer exists (e.g., deactivated type), it is silently ignored
Preferences are stored per user (userId-scoped key), not globally — switching accounts does not leak another user's defaults

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
shared_preferences or Hive (local storage)
apis
RegistrationPreferencesRepository (local storage abstraction)
data models
RegistrationPreferences
ActivityType
performance requirements
Preferences read must complete within 200ms — use SharedPreferences.getInstance() cached instance, not getInstance() per call
Pre-fill must not block the first frame render of the bottom sheet
security requirements
Preferences key must be scoped to userId to prevent cross-user data leakage
Do not store sensitive fields (notes) in preferences — only activityTypeId and durationMinutes
ui components
ActivityTypeStep
DurationStep

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement RegistrationPreferencesRepository as an abstract class with a SharedPreferences implementation — this makes the Cubit testable without touching the filesystem. In the Cubit's constructor or an init() method, fire the async preferences read and emit PreFillDefaults — do not await it synchronously before the Cubit is usable. Add a flag to ActivityRegistrationFormData: hasUserInteractedWithActivityType (bool) and hasUserInteractedWithDuration (bool). Set these flags when the user explicitly dispatches ActivityTypeSelected or DurationChanged.

In the PreFillDefaults handler, only apply each pre-fill value if the corresponding 'hasUserInteracted' flag is false. For writing back after success: listen to ActivityRegistrationSuccess state in a BlocListener in the bottom sheet container and call prefsRepository.saveLastRegistration(activityTypeId, durationMinutes).

Testing Requirements

Unit tests for RegistrationPreferencesRepository: test read returns null on first launch, test read returns stored values after write, test userId scoping (different users return different values). Cubit tests: test PreFillDefaults event sets fields correctly, test that PreFillDefaults is ignored when user has already interacted with a field (set a flag in state: hasUserInteractedWithActivityType). Widget test: verify sheet renders without pre-filled values on first frame, then pump a microtask and verify pre-filled values appear. Test the 'no override if user interacted' scenario.

Component
Activity Type Selection 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.