critical priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

updateActivityType(String activityTypeId) emits a new ActivityRegistrationStep with activityTypeId updated and validationErrors cleared for the activityType field
updateDate(DateTime date) emits a new ActivityRegistrationStep with date updated and validationErrors cleared for the date field
updateDuration(int minutes) emits a new ActivityRegistrationStep with durationMinutes updated and validationErrors cleared for the duration field
updateNotes(String? notes) emits a new ActivityRegistrationStep with notes updated — no validation error for notes (it is optional)
All update methods are no-ops if the current state is not ActivityRegistrationStep (guard against calling updates while submitting or in failure state)
advanceStep() validates the current step's required field(s) using ActivityRegistrationValidator rules. If invalid, emits ActivityRegistrationStep with validationErrors map populated for the failing field and currentStep unchanged
advanceStep() on the last step (notes) calls ActivityRegistrationService.submitRegistration() and transitions through ActivityRegistrationSubmitting → ActivityRegistrationSuccess or ActivityRegistrationFailure
advanceStep() on any non-last step with a valid field emits ActivityRegistrationStep with currentStep advanced to the next RegistrationStep value
goBack() emits ActivityRegistrationStep with currentStep set to the previous RegistrationStep — all field values are preserved (not reset)
goBack() on the first step is a no-op — it does not emit a new state or throw
Calling goBack() after a validation error clears the validationErrors map for the current step
Unit tests cover: each update method emits correct state, advanceStep with invalid field emits validation error without advancing, advanceStep with valid field advances step, goBack preserves field values, goBack on first step is no-op

Technical Requirements

frameworks
Flutter
BLoC
Dart
data models
ActivityRegistrationPayload
ValidationError
ActivityRecord
RegistrationStep
performance requirements
All update methods and goBack() must emit synchronously (no async operations) — state update must be < 1ms
advanceStep() on non-last steps must emit synchronously; only the final step triggers async submit
security requirements
Field update methods must not accept values from sources other than user input or validated lookups — no raw API responses passed directly as field values
ui components
ActivityTypeSelector widget (reads activityTypeId from state)
DatePickerField widget (reads date from state)
DurationSlider or DurationInput widget (reads durationMinutes from state)
NotesTextField widget (reads notes from state)
ValidationErrorText widget (reads validationErrors from state for the relevant field)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Each update method should follow the same pattern: guard (if state is not ActivityRegistrationStep return), then emit (state as ActivityRegistrationStep).copyWith(fieldName: newValue, validationErrors: updatedErrors). This pattern is safe, readable, and consistent. For advanceStep(), define a private _validateCurrentStep() method that returns a Map for the current step's required fields — this keeps advanceStep() clean. The per-step validation in advanceStep() should reuse the same rules defined in ActivityRegistrationValidator (task-004) rather than duplicating logic — call the validator with a partial payload constructed from the current step's fields.

Consider whether updateDuration should clamp values to 1–480 silently or emit a validation error — align with UX design, but make the behaviour explicit in tests before implementing.

Testing Requirements

Unit tests with flutter_test and bloc_test. Test each update method: (1) updateActivityType emits correct ActivityRegistrationStep with only activityTypeId changed, (2) updateDate clears date validation error when called with valid date, (3) updateDuration with value > 480 does not emit (or emits with validation error — define expected behaviour explicitly before implementing), (4) advanceStep from step 1 with null activityTypeId emits validationErrors without advancing, (5) advanceStep from step 1 with valid activityTypeId advances to step 2 and preserves date and duration, (6) goBack from step 2 returns to step 1 with all fields intact, (7) goBack from step 1 emits nothing (no state change), (8) advanceStep on final step with all fields valid triggers submit sequence and emits ActivityRegistrationSubmitting then ActivityRegistrationSuccess. Use mocked ActivityRegistrationService for test (8).

Component
Activity Registration Cubit
service medium
Epic Risks (3)
medium impact medium prob technical

The wizard Cubit manages multiple concurrent state slices (current step, each field value, submission status, error state). As the number of wizard steps grows, the state class can become unwieldy, making it difficult to reason about transitions, leading to subtle bugs where advancing a step resets a previously filled field.

Mitigation & Contingency

Mitigation: Use an immutable state model (copyWith pattern) with a separate sealed class per wizard step state. Keep the Cubit's emit calls minimal and always derive the next state from the current state to prevent accidental field resets. Document the state machine transitions explicitly in code comments.

Contingency: If state complexity becomes unmanageable, split into a parent WizardCubit (owns step navigation and submission) and per-step child Cubits (own individual field state), coordinating via a shared repository layer.

high impact medium prob scope

Organisation-specific compensation eligibility rules (e.g., activity type + duration thresholds) are business logic that may change independently of the app release cycle. Hardcoding these rules in ActivityRegistrationService means rule changes require a new app deployment, causing delays and potential financial errors if the deployed version uses outdated rules.

Mitigation & Contingency

Mitigation: Model compensation rules as configuration fetched from Supabase (stored per organisation), cached locally. ActivityRegistrationService reads from cache with a fallback to hardcoded defaults for offline scenarios. Design the rule schema to be extensible without code changes.

Contingency: If dynamic rules are not ready for initial release, ship with hardcoded rules and a feature flag that enables the remote-config path. Document the rule structure clearly so coordinators can trigger a rule update via a Supabase dashboard entry rather than a code deployment.

medium impact low prob integration

The last-used activity type stored in RegistrationPreferencesStore may become invalid if the organisation administrator deactivates that activity type between sessions. The Cubit would pre-populate a deleted type, and either the UI would show a missing item or submission would fail with a foreign-key constraint error.

Mitigation & Contingency

Mitigation: In RegistrationDefaultsManager, validate the retrieved last-used activity type ID against the current list of active types fetched from the activity type repository. If the stored ID is not in the active list, fall back to the first active type alphabetically.

Contingency: If validation cannot be performed offline, surface a non-blocking warning in the activity type step ('Your previously used activity type is no longer available') and require the user to make a new selection before advancing.