critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

ActivityRegistrationState is a sealed class (Dart 3 sealed or freezed union) with exactly 5 subclasses: ActivityRegistrationInitial, ActivityRegistrationStep, ActivityRegistrationSubmitting, ActivityRegistrationSuccess, ActivityRegistrationFailure
ActivityRegistrationInitial has no fields — it is the state before initializeWithDefaults() completes
ActivityRegistrationStep contains: currentStep (RegistrationStep enum with values: activityType, date, duration, notes or similar), activityTypeId (String?), date (DateTime?), durationMinutes (int?), notes (String?), validationErrors (Map<String, ValidationError> keyed by field name, empty map by default)
ActivityRegistrationStep exposes a copyWith method (generated by freezed or manually written) so individual fields can be updated immutably
ActivityRegistrationSubmitting has no fields — it is emitted while the async submit is in flight
ActivityRegistrationSuccess contains the optimistic ActivityRecord returned by ActivityRegistrationService.submitRegistration
ActivityRegistrationFailure contains: userFacingMessage (String, localised or ready for localisation), registrationError (RegistrationError, for logging/debugging)
RegistrationStep enum is defined in the same file or a co-located file and has a next() and previous() method returning the adjacent step or null if at boundary
All state classes implement == and hashCode (freezed handles this automatically; manual classes must override)
States are importable from a single barrel export file consistent with existing BLoC state conventions in the codebase

Technical Requirements

frameworks
Flutter
BLoC
Dart
data models
ActivityRecord
ValidationError
RegistrationError
ActivityRegistrationPayload
performance requirements
All state classes must be immutable — no mutable fields
copyWith on ActivityRegistrationStep must not perform deep copies of unchanged fields
security requirements
ActivityRegistrationStep must not store raw authentication tokens or session data as fields
userFacingMessage in ActivityRegistrationFailure must not include stack traces or internal error details

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Check the existing BLoC files in the codebase to determine whether the project uses freezed or manual sealed classes — match that pattern exactly. If freezed is used, annotate the union with @freezed and run build_runner. If manual sealed classes are used (Dart 3 sealed keyword), write each subclass as a final class extending the sealed base. The RegistrationStep enum's next()/previous() methods can be implemented as extension methods on the enum rather than methods on the enum itself if that is the codebase convention.

Keep all state definitions in lib/features/activity_registration/presentation/cubit/activity_registration_state.dart. Do not put business logic in state classes — states are pure data containers.

Testing Requirements

Unit tests with flutter_test. Test: (1) each state class can be instantiated with required fields, (2) ActivityRegistrationStep.copyWith correctly updates individual fields while preserving others, (3) equality — two ActivityRegistrationStep instances with identical fields are equal, (4) RegistrationStep.next() returns correct next step and null at last step, (5) RegistrationStep.previous() returns correct previous step and null at first step, (6) ActivityRegistrationFailure.userFacingMessage is set correctly from constructor. No widget tests required for this task.

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.