high priority medium complexity infrastructure pending backend specialist Tier 1

Acceptance Criteria

FieldConfig is a sealed class (or freezed union) with variants: TextFieldConfig, TextareaFieldConfig, CheckboxFieldConfig, RadioFieldConfig, SelectFieldConfig, NumberFieldConfig, DateFieldConfig, WayForwardSectionConfig — unrecognised type values are skipped with a logged warning, not thrown
Each FieldConfig carries: fieldId (String), label (String), isRequired (bool), order (int), and type-specific properties (e.g., options for radio/select, min/max for number)
Loader queries the Supabase table `org_field_configs` filtered by org_id; row-level security (RLS) on Supabase ensures cross-org isolation without client-side filtering
On success the Riverpod provider emits AsyncValue.data(List<FieldConfig>) sorted ascending by the `order` field
On network failure the loader reads from ReportSchemaCache (task-002 dependency) and emits cached data; if cache is also empty, emits AsyncValue.error with an OfflineException
Cache write happens after every successful network fetch; stale cache is acceptable for offline use
Malformed JSON rows are skipped individually (per-row try/catch) — a single bad row must not prevent the rest from loading
Provider is family-scoped by orgId: orgFieldConfigProvider(orgId) so multiple orgs can be cached independently
Empty field list (org has no custom config) is a valid state: emits AsyncValue.data([]) — not an error
All Supabase calls use the authenticated client; unauthenticated calls must fail with AuthException before network request is made

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
freezed (recommended for FieldConfig union)
apis
Supabase PostgREST .from('org_field_configs').select().eq('org_id', orgId)
ReportSchemaCache.read(orgId)
ReportSchemaCache.write(orgId, data)
data models
FieldConfig (sealed union)
TextFieldConfig
TextareaFieldConfig
CheckboxFieldConfig
RadioFieldConfig
SelectFieldConfig
NumberFieldConfig
DateFieldConfig
WayForwardSectionConfig
performance requirements
Network fetch must complete within 3 seconds on a 3G connection; otherwise fall back to cache
JSON deserialization of up to 50 field configs must complete within 100ms on a mid-range device
Provider must not rebuild if re-invoked with the same orgId while data is already loaded (Riverpod keepAlive or cacheFor)
security requirements
Supabase RLS policy must enforce org_id isolation — verify in integration tests
FieldConfig objects must not contain raw Supabase API keys or tokens
Cache storage must use encrypted SharedPreferences or flutter_secure_storage if field labels are considered org-confidential

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement as an AsyncNotifierFamily provider: `final orgFieldConfigProvider = AsyncNotifierProvider.family, String>()`. The notifier's build() method should: (1) attempt Supabase fetch, (2) on success write to cache and return, (3) on failure read cache. Use freezed for FieldConfig for pattern matching in form renderer. Map the `field_type` string to the correct variant using a switch expression with a default case that logs and returns null (then filter nulls).

Keep the Supabase table query in a dedicated OrgFieldConfigRepository class for testability. The WayForwardSectionConfig is a special case — it acts as a section boundary in the form, not a regular input field; ensure the renderer handles it distinctly.

Testing Requirements

Unit tests (flutter_test): (1) happy path — mock Supabase returning valid JSON for all 8 field types, assert correct FieldConfig variants and sort order, (2) unknown field type 'signature' is skipped, remaining configs returned, (3) malformed row (missing required key) is skipped, (4) network error triggers cache read — mock cache returns stale data, assert AsyncValue.data emitted, (5) network error + empty cache emits AsyncValue.error(OfflineException), (6) empty result set returns AsyncValue.data([]), (7) unauthenticated call throws before Supabase request. Use Riverpod ProviderContainer with overrides for the Supabase client mock. Integration test against a Supabase staging environment: insert 3 field configs for a test org, fetch, assert count and types. Minimum 85% line coverage.

Component
Organisation Field Config Loader
infrastructure 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.