critical priority medium complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

ExportPreviewState is a sealed class with at least four subtypes: ExportPreviewInitial, ExportPreviewLoading, ExportPreviewLoaded, and ExportPreviewError
ExportPreviewLoaded contains: List<BufdirCategoryRow> categories, ScopeMetadata scope, PeriodMetadata period, int totalRecordCount, List<UnmappedTypeWarning> unmappedTypes, List<MissingDataWarning> missingFields
BufdirCategoryRow exposes: String categoryId, String categoryName, int activityCount, int participantCount
ScopeMetadata exposes: String organisationId, String organisationName, String? unitId, String? unitName
PeriodMetadata exposes: DateTime startDate, DateTime endDate, String label
UnmappedTypeWarning exposes: String activityTypeId, String activityTypeName, String remediationHint
MissingDataWarning exposes: String fieldName, String plainLanguageExplanation, String remediationHint
ExportPreviewCubit.fetchPreview(ScopeMetadata scope, PeriodMetadata period) emits ExportPreviewLoading then ExportPreviewLoaded or ExportPreviewError
fetchPreview must never call any endpoint that finalises or persists export records
All state classes are immutable (final fields, const constructors where possible)
Cubit is registered via a BlocProvider and is injectable for testing
Unit tests pass for all state transitions including error propagation

Technical Requirements

frameworks
Flutter
flutter_bloc
BLoC
apis
BufdirExportService.fetchPreview (Supabase Edge Function, preview-only endpoint)
data models
activity
activity_type
bufdir_column_schema
bufdir_export_audit_log
performance requirements
State construction must complete in under 5 ms for up to 50 category rows
Cubit must not retain references to previous states after transition to avoid memory leaks
security requirements
fetchPreview must pass the user's JWT from Supabase Auth; no service role key on the client
Scope and period parameters must be validated (non-null, valid UUID format) before dispatch to service
No PII fields stored in state beyond what is strictly needed for display

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use sealed classes (Dart 3) for ExportPreviewState to enable exhaustive switch in the UI layer. Keep BufdirCategoryRow, ScopeMetadata, and PeriodMetadata as plain Dart value objects (no BLoC dependency) so they can be reused by the mapper in task-002. Implement ExportPreviewCubit with an injected BufdirExportService interface — never depend on the concrete implementation directly. This makes the Cubit testable without a real network.

Avoid storing raw JSON in state; always deserialise to typed objects in the mapper before emitting state. Consider using Equatable for all state and value classes to simplify widget rebuild control and test assertions.

Testing Requirements

Unit tests using flutter_test and bloc_test. Test each state transition: initial → loading, loading → loaded (happy path with at least 3 category rows, 1 unmapped warning, 1 missing-data warning), loading → error (network failure, HTTP 403, HTTP 500). Mock BufdirExportService using Mockito or mocktail. Verify that fetchPreview emits states in correct order.

Verify that ExportPreviewLoaded correctly parses a sample DTO fixture. Achieve 100% branch coverage on ExportPreviewCubit.

Component
Export Preview Panel
ui medium
Epic Risks (2)
medium impact medium prob technical

The preview panel requires a round-trip to the edge function to compute aggregated counts. If this call takes 5–15 seconds for large scopes, users may assume the app has frozen and navigate away, potentially triggering duplicate preview requests or leaving the export in an undefined state.

Mitigation & Contingency

Mitigation: Show an immediate skeleton loading state on the preview panel as soon as the period and scope are confirmed, with named stage labels (e.g. 'Querying activities…', 'Computing totals…') streamed from the edge function's progress events. Set a clear user-visible timeout with a retry option. Pre-warm the edge function with a lightweight ping when the scope selector is opened.

Contingency: If preview latency consistently exceeds 10 seconds for large scopes, cache the preview payload in local BLoC state and allow the coordinator to proceed to confirmation without re-fetching if scope and period have not changed since the last preview.

high impact medium prob scope

The export preview panel contains dynamic content (warning badges, loading skeletons, aggregated counts) that must be announced correctly by VoiceOver and TalkBack. Incorrect semantics annotations could make the preview unreadable for blind coordinators, violating the project's WCAG 2.2 AA accessibility mandate.

Mitigation & Contingency

Mitigation: Implement semantic annotations using Flutter's Semantics widget with explicit labels for all dynamic content. Use live region announcements for loading state transitions. Schedule a dedicated accessibility review session with a screen reader user (Blindeforbundet has relevant expertise) before marking the epic complete.

Contingency: If the preview panel cannot be made fully screen-reader-accessible in time for launch, ship a simplified text-only summary mode activated by a toggle at the top of the preview panel, which renders all data as a single readable paragraph with no interactive badges.