Implement draft recovery on BLoC initialisation
epic-expense-type-selection-core-services-task-011 — In ExpenseSelectionBloc, add an initialise event that calls ExpenseTypeRepository.loadDraft on first load. If a draft exists, hydrate the state with the persisted selection and immediately trigger recalculation to restore a valid ExpenseCalculationResult. If no draft exists, start with empty selection. This prevents the data loss scenario described in the epic where peer mentors abandon mid-flow registrations.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Add a handler for InitialiseExpenseSelection in the BLoC constructor registration. Dispatch the event in the BLoC constructor: `add(const InitialiseExpenseSelection())`. Pass the claimContextId to the BLoC constructor so it is available throughout the BLoC lifetime. The event handler should: (1) call await repository.loadDraft(claimContextId), (2) compute the initial selectedIds (draft or empty), (3) compute disabledTypeIds via computeDisabledTypes, (4) emit the hydrated state, (5) trigger recalculation if selectedIds is non-empty.
Wrap the entire handler in try/catch so a draft load failure (e.g., storage permission error on first launch) falls back to an empty state gracefully. This addresses the UX concern raised in the workshop: HLF peer mentors with 380 registrations per year need mid-flow recovery to be seamless.
Testing Requirements
Unit tests with flutter_test and bloc_test. Mock ExpenseTypeRepository.loadDraft. Test cases: (1) loadDraft returns a valid draft → emitted state has hydrated selectedIds and recalculation is triggered, (2) loadDraft returns null → emitted state has empty selectedIds, (3) loadDraft throws → state is empty and error is swallowed gracefully. Verify that computeDisabledTypes is invoked on the hydrated set (check disabledTypeIds in the emitted state).
Use fakeAsync to verify recalculation is triggered after hydration.
The per-km reimbursement rate and transit zone amounts must be read from org-specific configuration stored in Supabase. If the rate configuration table or RLS policies are not yet deployed when this epic runs, the calculation service cannot be completed and integration tests will fail.
Mitigation & Contingency
Mitigation: Define a RateConfigRepository interface and inject a stub implementation with default HLF rates from day one; write the real Supabase adapter in parallel and swap via dependency injection before merge.
Contingency: If org rate config is delayed beyond this epic's window, ship with the default-rate stub and log a prominent warning; calculate with defaults and surface a 'rates not confirmed' notice in the UI preview.
If the peer mentor opens an expense claim on two devices simultaneously, the local draft and the Supabase record may diverge. The repository's last-write-wins strategy could silently overwrite a valid selection with a stale one.
Mitigation & Contingency
Mitigation: Add an updated_at timestamp to the draft record and reject saves where the server timestamp is newer than the local copy; surface a conflict resolution prompt rather than silently overwriting.
Contingency: If conflict resolution UI is out of scope, fall back to server-authoritative reads on app foreground resume and discard local draft, notifying the user that their draft was refreshed from the server.