high priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

saveLastUsedActivityType(String activityTypeId) writes activityTypeId to RegistrationPreferencesStore under the designated key
After a successful call to saveLastUsedActivityType('type-abc'), a subsequent call to getDefaults() returns lastUsedActivityTypeId == 'type-abc'
saveLastUsedActivityType is called by the activity registration submission handler only after the Supabase activity record is confirmed saved (not before, to avoid stale defaults on failure)
If activityTypeId is an empty string, saveLastUsedActivityType throws an ArgumentError — callers must provide a non-empty ID
saveLastUsedActivityType does not trigger any UI update directly — it is a pure data operation; upstream Cubit/BLoC is responsible for triggering any UI refresh
The method is fire-and-forget safe: if the local store write fails, the error is logged but does NOT prevent the registration success confirmation from being shown to the user
Unit test exists demonstrating the round-trip: save → get returns the saved value

Technical Requirements

frameworks
Flutter
Riverpod or BLoC
apis
RegistrationPreferencesStore (local key-value storage)
data models
RegistrationDefaults
performance requirements
Write to shared_preferences completes asynchronously without blocking the UI thread
Caller should await the Future but treat it as non-critical (fire-and-forget pattern acceptable at the call site)
security requirements
Only opaque activity type IDs are persisted — no user data or activity content is stored locally

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

The ArgumentError guard for empty strings should use assert() in debug mode and an explicit throw in production (or simply use assert with a descriptive message if the team convention uses asserts for preconditions). Do not add a guard for null — Dart's null safety enforces non-null at compile time. At the call site (activity registration Cubit), call saveLastUsedActivityType() using unawaited() from dart:async to make the fire-and-forget intent explicit and suppress lint warnings about discarded futures. Consider adding a thin integration test that exercises the full path: wizard selects activity type → registration submits → preference is written → new wizard session reads the saved default — this validates the 'under two clicks' promise end-to-end.

Testing Requirements

Unit tests using flutter_test with mocked IRegistrationPreferencesStore: test that saveLastUsedActivityType('valid-id') calls store.write(key, 'valid-id') exactly once, test that saveLastUsedActivityType('') throws ArgumentError, test that if store.write() throws, the error is caught and logged (not rethrown), test round-trip integration with RegistrationDefaultsManager using an in-memory fake store implementation (not a mock) — save then get returns the saved value. Target 100% branch coverage on this method.

Component
Registration Defaults Manager
service low
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.