Implement Report Form Orchestrator Bloc
epic-structured-post-session-report-form-engine-task-010 — Build the Bloc/Riverpod state manager coordinating the full form lifecycle: schema load, field value mutations, draft auto-save (debounced 2s), cross-field validation, and final submission. Expose FormState with schema, currentValues map, validationErrors map, draftStatus, and submissionStatus. Handle interruption recovery by rehydrating draft from repository on init. Emit typed events: FieldChanged, SaveDraft, Submit, Reset.
Acceptance Criteria
Technical Requirements
Execution Context
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
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.
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.
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.
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.