critical priority high complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Bloc exposes a sealed FormState with sub-states: FormLoading, FormReady (schema, currentValues, validationErrors, draftStatus, submissionStatus), FormSubmitted, FormError
On init, bloc loads the report schema from the schema repository and rehydrates any existing draft from the draft repository within 1 second
FieldChanged event updates currentValues map and triggers cross-field validation within 100ms
Draft auto-save is debounced exactly 2 seconds after the last FieldChanged event and persists to repository without blocking UI
draftStatus transitions: idle β†’ saving β†’ saved (or saveFailed) are correctly emitted and accessible in FormReady state
SaveDraft event forces an immediate (non-debounced) draft save
Submit event triggers full cross-field validation; if any required field is empty or any validation rule fails, submissionStatus = validationFailed and validationErrors map is populated with field IDs as keys
Successful submission transitions to FormSubmitted state and clears the persisted draft from the repository
Reset event rehydrates state from the original schema with empty values and clears draftStatus and validationErrors
All bloc event handlers are pure and side-effect free; all I/O delegated to injected repositories
Bloc disposes debounce timer on close to prevent memory leaks
Bloc is unit-testable in isolation with mock repositories (no Flutter widget dependency)

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
Riverpod (for DI/provider scope if applicable)
apis
PostSessionReportRepository (draft CRUD)
ReportSchemaRepository (schema load)
CrossFieldValidatorService
data models
ReportFormSchema
FieldConfig
DraftReport
ValidationResult
WayForwardItem
performance requirements
Schema load + draft rehydration on init must complete within 1000ms on mid-range devices
FieldChanged β†’ state emission latency < 100ms
Debounce timer must not accumulate multiple timers; cancel previous before starting new
security requirements
Draft persisted locally must not include sensitive fields (e.g., personal health notes) in plaintext if device encryption is unavailable β€” use Supabase-encrypted draft storage
Submission payload must be validated server-side via Supabase RLS; client validation is a UX guard only
No PII logged in bloc debug output

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use flutter_bloc's Bloc (not Cubit) to enforce explicit event typing. Define a sealed FormEvent hierarchy: FieldChanged(fieldId, value), SaveDraft(), Submit(), Reset(). Define a sealed FormState hierarchy using freezed for immutability and copyWith support. Use a StreamSubscription + Timer for debounce rather than RxDart to minimize dependencies.

Inject repositories via constructor (compatible with both Riverpod and BlocProvider). Guard against concurrent submissions with a submitting flag in state. For cross-field validation, delegate to a pure CrossFieldValidatorService that takes the schema + currentValues and returns a Map. Avoid storing the schema in a local variable inside event handlers β€” always read from state to keep handlers referentially transparent.

Consider using Equatable on all state/event classes for efficient stream deduplication.

Testing Requirements

Unit tests using flutter_test and bloc_test package. Test every event→state transition in isolation with mock repositories. Specific scenarios: (1) init with no existing draft emits FormReady with empty values; (2) init with existing draft rehydrates currentValues correctly; (3) FieldChanged debounce fires SaveDraft exactly once after 2s of inactivity; (4) rapid FieldChanged events within 2s do not trigger multiple saves; (5) Submit with missing required fields emits FormReady with validationErrors populated; (6) Submit with valid values emits FormSubmitted and draft is cleared; (7) Reset restores empty schema values; (8) repository failure on draft save emits draftStatus = saveFailed without crashing. Target 95% branch coverage on the bloc class.

Component
Report Form Orchestrator
service high
Dependencies (4)
Build the service layer validator that evaluates field values against schema-defined rules: required, minLength, maxLength, pattern, min/max for numeric fields. Return typed ValidationResult objects with field-level error messages in Norwegian and English. Designed as a pure function class with no external dependencies, enabling fast unit testing of all validation paths. epic-structured-post-session-report-form-engine-task-006 Create the service layer for way-forward item lifecycle management: create, update, delete, and list items linked to a report. Implement business rules for coordinator assignment, status transitions (pending β†’ in_progress β†’ completed), and duplicate detection. Coordinate with WayForwardItemRepository for persistence and emit events for coordinator notification triggers. epic-structured-post-session-report-form-engine-task-007 Build the service that resolves the active report schema for a given org and report type, combining data from OrgFieldConfigLoader and ReportSchemaCache. Handle schema versioning, apply org-level field visibility overrides, and return a resolved ReportSchema domain object ready for form rendering. Expose a Riverpod provider with proper error and loading states. epic-structured-post-session-report-form-engine-task-008 Create the Supabase-backed repository for persisting complete post-session reports including draft state, submitted reports, and metadata. Define the PostSessionReport model with org_id, feature_id, peer_mentor_id, schema_version, field_values map, and submission timestamp. Implement save-draft, submit, and fetch-by-id operations with proper RLS. epic-structured-post-session-report-form-engine-task-003
Epic Risks (3)
high impact medium prob technical

Dynamically rendered form fields built from runtime JSON schema are significantly harder to make accessible than statically declared widgets β€” Flutter's Semantics tree must be correct for every possible field type and every validation state. Failures here block the entire feature for Blindeforbundet's visually impaired peer mentors.

Mitigation & Contingency

Mitigation: Define WCAG 2.2 AA semantics requirements for each field type before implementation and write widget tests using Flutter's SemanticsController for every type. Include a real-device VoiceOver test session in the acceptance gate for this epic before marking it done.

Contingency: If dynamic semantics prove too difficult to get right generically, implement field-type-specific Semantics wrappers (one per supported field type) instead of a single generic renderer, accepting slightly more code duplication in exchange for reliable accessibility.

high impact medium prob technical

The report-form-orchestrator must manage a complex state machine β€” schema loading, draft persistence, per-field validation, submission retries, and error recovery β€” across multiple async operations. Incorrect state transitions could result in lost user data, double submissions, or UI freezes.

Mitigation & Contingency

Mitigation: Define all Bloc states and events explicitly as sealed classes before writing any logic. Use a state machine diagram reviewed by the team before implementation. Write exhaustive Bloc unit tests covering every state transition, including concurrent events and network interruption mid-submission.

Contingency: If Bloc complexity becomes unmanageable, extract draft persistence into a separate DraftManagerCubit and keep report-form-orchestrator focused solely on the submit workflow. The additional granularity makes each component independently testable.

medium impact low prob scope

Organisations may require field types beyond the five currently specified (text, multiline, checkbox group, radio, date). If a new type is discovered during pilot testing, the dynamic-field-renderer must be extended, potentially requiring changes across multiple layers.

Mitigation & Contingency

Mitigation: Design dynamic-field-renderer as a registry of field-type renderers with a clear extension point. Document the pattern for adding a new field type so that it can be done in one file without touching existing renderers.

Contingency: If an unhandled field type is encountered at runtime, dynamic-field-renderer renders a labelled plain-text fallback widget and logs a warning so the missing type is surfaced in monitoring, preventing a crash while making the gap visible.