Export Preview Panel state model and Cubit
epic-bufdir-report-export-user-interface-task-001 — Define the ExportPreviewState data model covering aggregated activity counts per Bufdir category, scope metadata, period metadata, warning lists (unmapped types, missing data), and loading/error states. Implement ExportPreviewCubit using flutter_bloc that exposes fetchPreview(scope, period) which calls the BufdirExportService preview endpoint without finalising any record.
Acceptance Criteria
Technical Requirements
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.
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.
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.