critical priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

WizardStateManager is implemented as a Cubit (preferred) or BLoC class with typed state sealed classes covering: WizardInitial, WizardStepActive(currentStep, totalSteps, draftId), WizardStepTransitioning, WizardCompleted, WizardAbandoned, and WizardError(message)
nextStep() validates via CognitiveLoadRuleEngine that currentStep < 5 before transitioning; emits WizardError if step limit would be exceeded
previousStep() transitions back one step and does not trigger CognitiveLoadRuleEngine validation; cannot go below step index 0
jumpToStep(int targetStep) only allows jumping to already-visited or adjacent steps; throws assertion error for invalid indices
abandonDraft() calls WizardDraftRepository.deleteDraft(draftId) and emits WizardAbandoned state; all resources are released
Every step transition (next, previous, jump) triggers WizardDraftRepository.autoSave() synchronously before emitting new state; save failure emits WizardError without advancing state
WizardStateManager exposes a Stream<WizardProgressSnapshot> that WizardProgressIndicator can subscribe to without holding a reference to the full Cubit
WizardStateManager is registered via a dependency injection provider (Riverpod or equivalent) scoped to the wizard screen lifecycle, not global app scope
State is not lost on widget rebuild — re-subscribing to the stream returns the latest state immediately
On app resume from background, state is restored from WizardDraftRepository if a draftId exists and draft TTL has not expired
All public methods are documented with Dart doc comments including parameter contracts and state transition descriptions

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
Riverpod (for DI scoping)
apis
WizardDraftRepository.autoSave()
WizardDraftRepository.deleteDraft()
CognitiveLoadRuleEngine.validateStepCount()
WizardDraftRepository.loadDraft()
data models
WizardDraft
WizardStep
WizardProgressSnapshot
CognitiveLoadViolation
performance requirements
State transitions must complete in under 50ms (excluding async I/O)
Auto-save must not block the UI thread — execute via unawaited Future or isolate if draft serialization exceeds 10ms
Stream emissions must be deduplicated — identical sequential states must not re-emit
security requirements
draftId must be a cryptographically random UUID (dart:math Random.secure()) — never sequential
WizardDraftRepository auto-save must honour Supabase Row Level Security (RLS) policies so a user cannot overwrite another user's draft
abandonDraft() must hard-delete draft data from Supabase (not soft-delete) to prevent stale sensitive data lingering

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use a sealed class hierarchy for WizardState to enforce exhaustive pattern matching at call sites — this prevents missed state handling as the wizard evolves. The auto-save should use a debounce strategy only if transitions happen faster than 300ms in sequence (e.g., rapid taps); otherwise save immediately. Keep WizardStateManager free of any UI dependencies — it should be a pure Dart class with no Flutter imports except for foundation.dart if needed. Scope the Cubit using a Riverpod AutoDisposeNotifierProvider so it is torn down when the wizard screen is popped, preventing memory leaks.

If CognitiveLoadRuleEngine returns a violation, do NOT swallow it silently — surface it as a WizardError state so the UI can display an accessible error message via PlainLanguageErrorDisplay. Avoid storing step widget instances inside the state — store only step indices and data payloads; the UI layer owns rendering.

Testing Requirements

Write flutter_test unit tests covering: (1) all six state transitions including guard conditions, (2) auto-save invocation count per transition using a mock WizardDraftRepository, (3) CognitiveLoadRuleEngine rejection path when step index would exceed 5, (4) abandonDraft() completes even if the delete RPC returns an error (graceful degradation), (5) stream deduplication — identical state emitted twice should result in one downstream event. Write an integration test using a real Supabase test project that verifies auto-save round-trips correctly and draft restoration after simulated app restart. Target ≥90% line coverage on WizardStateManager. Use bloc_test package for BLoC/Cubit state assertion helpers.

Component
Wizard Draft Repository
data low
Epic Risks (4)
high impact medium prob technical

The error message registry and help content registry both depend on bundled JSON assets loaded at startup. If asset loading fails silently (e.g. malformed JSON, missing pubspec asset declaration), the entire plain-language layer falls back to empty strings or raw error codes, breaking the accessibility guarantee app-wide.

Mitigation & Contingency

Mitigation: Implement eager validation of both assets during app initialisation with an assertion failure in debug mode and a structured error log in release mode. Add integration tests that verify asset loading in the Flutter test harness on every CI run.

Contingency: Ship a hardcoded minimum-viable fallback message set directly in Dart code so the app always has at least a safe generic message, preventing a blank or code-only error surface.

medium impact medium prob dependency

The AccessibilityDesignTokenEnforcer relies on dart_code_metrics custom lint rules. If the lint toolchain is not already configured in the project's CI pipeline, integrating a new linting plugin may cause unexpected build failures or require significant CI configuration work beyond the estimated scope.

Mitigation & Contingency

Mitigation: Audit the existing dart_code_metrics configuration in the project before starting implementation. Scope the lint rules to a separate Dart package that can be integrated incrementally, starting with the most critical rule (hard-coded colors) and adding others in subsequent iterations.

Contingency: Fall back to Flutter test-level assertions (using the cognitive-accessibility-audit utility) to catch violations in CI if the lint plugin integration is delayed, preserving enforcement coverage without blocking the epic.

medium impact low prob technical

WizardDraftRepository must choose between shared_preferences and Hive for local persistence. Choosing the wrong store for the data volume (e.g. shared_preferences for complex nested wizard state) can lead to serialisation bugs or performance degradation, particularly on lower-end Android devices used by some NHF members.

Mitigation & Contingency

Mitigation: Define a clean repository interface first and implement shared_preferences as the initial backend. Profile serialisation round-trip time with a realistic wizard state payload (≈10 fields) before committing to either store.

Contingency: Swap the persistence backend behind the repository interface without touching wizard UI code, which is possible precisely because the repository abstraction isolates the storage detail.

medium impact high prob scope

The AccessibilityDesignTokenEnforcer scope could expand significantly if a large portion of existing widgets use hard-coded values. Discovering widespread violations during this epic would force either a major refactor or a decision to exclude legacy components, potentially reducing the enforcer's coverage and value.

Mitigation & Contingency

Mitigation: Run a preliminary audit of existing widgets using a simple grep for hard-coded hex colors and raw pixel values before implementation begins. Use the results to set a realistic remediation boundary for this epic and log all out-of-scope violations as tracked tech-debt items.

Contingency: Scope the enforcer to new and modified components only (via file-path filters in dart_code_metrics config), shipping a partial but immediately valuable coverage rather than blocking the epic on full-codebase remediation.