medium priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

WayForwardTaskService exposes: createItem(WayForwardItemInput) → Future<WayForwardItem>, updateItem(String id, WayForwardItemUpdate) → Future<WayForwardItem>, deleteItem(String id) → Future<void>, listItems(String reportId) → Future<List<WayForwardItem>>
Status machine enforced: pending→in_progress and pending→completed are valid; in_progress→completed is valid; any backward transition (completed→pending) throws InvalidStatusTransitionException
Duplicate detection: createItem() checks for an existing item with the same reportId + title (case-insensitive trim) and throws DuplicateWayForwardItemException if found
Coordinator assignment: assignedCoordinatorId must reference a valid user with the 'coordinator' role in the current org; invalid IDs throw InvalidCoordinatorException — validation is done via a IUserRoleValidator dependency injected into the service
On successful createItem() or status transition to in_progress, service emits a WayForwardEvent via an event Stream for downstream notification triggers; event carries itemId, reportId, assignedCoordinatorId, and eventType
deleteItem() on a completed item throws CannotDeleteCompletedItemException — completed items are immutable
listItems() returns items sorted by createdAt ascending; empty list is valid and not an error
All write operations are transactional: if persistence fails, no event is emitted
Service has no direct Supabase dependency — only depends on IWayForwardItemRepository and IUserRoleValidator interfaces
All exceptions extend a WayForwardServiceException base class for consistent catch handling in the BLoC layer

Technical Requirements

frameworks
Dart
Riverpod
apis
IWayForwardItemRepository (CRUD)
IUserRoleValidator.isCoordinator(orgId, userId) → Future<bool>
data models
WayForwardItem
WayForwardItemInput
WayForwardItemUpdate
WayForwardStatus (enum: pending, in_progress, completed)
WayForwardEvent
performance requirements
createItem() and updateItem() must complete within 2 seconds under normal network conditions
Duplicate check must be done in a single Supabase query (not a separate listItems() call) to avoid race conditions
security requirements
Coordinator assignment must be validated server-side (Supabase RLS) in addition to client-side check in IUserRoleValidator
reportId must belong to the authenticated user's org — service should verify ownership before any mutation
Deleted items should be soft-deleted (deleted_at timestamp) to preserve audit trail for Bufdir reporting requirements

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use the service layer pattern: WayForwardTaskService is a plain Dart class injected with IWayForwardItemRepository and IUserRoleValidator. Register as a Riverpod Provider so it can be overridden in tests. Event emission: use a StreamController.broadcast() owned by the service; expose as Stream get events. Status transition validation: extract into a private _validateTransition(WayForwardStatus from, WayForwardStatus to) method that throws or returns cleanly.

Duplicate detection query: `repository.findByReportIdAndTitle(reportId, title.trim().toLowerCase())` — the repository is responsible for case-insensitive matching. For the coordinator validation, keep the IUserRoleValidator call as a Future so the service can be tested with both sync and async fakes. The WayForwardItem domain object should be immutable (freezed or const constructor with copyWith). Do not couple the notification trigger logic directly in the service — only emit the event; a separate coordinator notification service listens to the stream.

Testing Requirements

Unit tests with mocked IWayForwardItemRepository and IUserRoleValidator: (1) createItem() happy path — verify repository.save() called and event emitted, (2) createItem() duplicate title → DuplicateWayForwardItemException, no repository call, (3) createItem() invalid coordinator → InvalidCoordinatorException, (4) updateItem() valid pending→in_progress transition emits event, (5) updateItem() invalid completed→pending transition → InvalidStatusTransitionException, (6) deleteItem() on pending item succeeds, (7) deleteItem() on completed item → CannotDeleteCompletedItemException, (8) repository throws StorageException → service propagates as WayForwardServiceException wrapping original, (9) listItems() returns items sorted by createdAt. Test event stream: subscribe before operation, assert event received with correct payload. Minimum 90% branch coverage.

Component
Way Forward Task Service
service medium
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.