critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

WizardDraftRepository exposes async methods: saveDraft(WizardDraft), loadDraft(String wizardType), deleteDraft(String draftId), and listDrafts() returning a List<WizardDraft>
saveDraft() performs an upsert (insert or update) against the `wizard_drafts` Supabase table using the authenticated user's JWT — no service role key usage
loadDraft() returns null (not an exception) when no active draft exists for the given wizardType
deleteDraft() completes silently if the draft does not exist — idempotent delete
All four methods propagate typed WizardDraftException (not raw SupabaseException) with human-readable English messages for error handling upstream
An optimistic local cache (in-memory map) prevents redundant Supabase reads on subsequent loadDraft() calls within the same app session
saveDraft() is debounced with a 500ms delay so rapid step transitions do not flood Supabase with upsert requests
On network failure in saveDraft(), the draft is queued locally and retried automatically on next connectivity restoration (basic retry — full offline sync is out of scope)
The repository is exposed as a Riverpod Provider (wizardDraftRepositoryProvider) and is testable via dependency injection of a mock SupabaseClient
WizardDraft Dart model includes: id, userId, wizardType, currentStepIndex, stepData (Map<int, Map<String, dynamic>>), createdAt, updatedAt, expiresAt

Technical Requirements

frameworks
Flutter
Riverpod
Supabase
apis
Supabase REST API (wizard_drafts table)
data models
WizardDraft
WizardStepData
performance requirements
saveDraft() must complete (or queue) within 100ms from call site on the UI thread — use compute/isolate if serialization is heavy
loadDraft() cache hit must return within 1ms (in-memory map lookup)
Debounce delay of 500ms on saveDraft() to prevent write flooding on fast step transitions
security requirements
Only the Supabase anon/user JWT is used — service role key must never appear in client-side Dart code
stepData serialization must strip any fields marked as sensitive before storing (configurable per wizard type)
Draft deletion must be confirmed server-side via Supabase RLS — client must not assume success without a 2xx response

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use a single abstract WizardDraftRepository interface plus a SupabaseWizardDraftRepository concrete implementation — this allows a MockWizardDraftRepository for tests and future alternative backends. The debounce can be implemented with a simple Timer-based approach: cancel the previous timer on each saveDraft() call and fire after 500ms of inactivity. For the optimistic cache, a `Map` keyed by wizardType is sufficient — invalidate the cache entry on deleteDraft() and update it on saveDraft() before the Supabase upsert completes. For retry-on-reconnect, listen to Supabase's connectivity stream or use a simple `Connectivity` package listener — keep the queued draft in memory and call saveDraft() again on reconnect.

Do not persist the queue to disk in this task. WizardDraft serialization: implement toJson()/fromJson() manually or use json_serializable — ensure int keys in stepData survive JSON round-trip (JSON keys are strings, so deserialize '0', '1' back to int). Expose the repository via a Riverpod Provider, not a singleton, to support test override with ProviderContainer.overrideWith().

Testing Requirements

Unit tests: use flutter_test with a mock SupabaseClient (via mockito or mocktail) to test all four CRUD methods in isolation — cover happy paths, null returns, error propagation, and debounce behavior. Cache tests: verify that a second loadDraft() call within the same session does not trigger a second Supabase read (assert mock is called exactly once). Error handling tests: simulate SupabaseException (network error, RLS violation) and assert WizardDraftException is thrown with correct message. Debounce test: call saveDraft() five times in rapid succession and assert the mock upsert is called only once after the debounce delay.

Integration tests: run against a local Supabase Docker instance with real JWT tokens — test RLS (user A cannot read user B's draft), upsert idempotency, and null return on missing draft. Provider test: verify wizardDraftRepositoryProvider resolves without error in a ProviderContainer.

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.