critical priority medium complexity integration pending integration specialist Tier 1

Acceptance Criteria

BufdirExportService exposes a fetchPreview(String organisationId, String? unitId, DateTime startDate, DateTime endDate) method returning Future<ExportPreviewDto>
The HTTP call targets the Supabase Edge Function route dedicated to preview (e.g. /bufdir-export/preview) and NOT any export-commit route
The Authorization header is populated with the active Supabase session JWT on every call
ExportPreviewMapper.fromDto(ExportPreviewDto dto) returns a correctly populated ExportPreviewLoaded state or throws a typed MappingException
HTTP 4xx errors are mapped to ExportPreviewError with a user-facing message (e.g. 'You do not have permission to preview this report')
HTTP 5xx errors are mapped to ExportPreviewError with a generic message and the status code logged for diagnostics
Network timeouts (>15 s) are caught and surfaced as ExportPreviewError with a timeout-specific message
ExportPreviewCubit.fetchPreview integration test passes against a mock Edge Function that returns a valid DTO
No export audit log entry (bufdir_export_audit_log) is created during a preview call — verified by querying the database after the test
Service and mapper are independently unit-testable

Technical Requirements

frameworks
Flutter
flutter_bloc
BLoC
apis
Supabase Edge Functions (Deno) — /bufdir-export/preview endpoint (REST, POST)
Supabase Auth — session JWT injection via supabase_flutter client
data models
bufdir_column_schema
bufdir_export_audit_log
activity
activity_type
performance requirements
Network call must time out after 15 seconds with a typed error
DTO-to-state mapping must complete in under 10 ms for a 100-row response
security requirements
JWT attached to every request via Supabase client — never stored in a local variable or logged
Service role key must never be used client-side; rely on RLS on the edge function
Scope parameters (organisationId, unitId) validated as non-empty UUIDs before the HTTP call to prevent injection
Response body must be validated against ExportPreviewDto schema before mapping; malformed payloads rejected with a typed error

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define ExportPreviewDto as a plain Dart class with a fromJson factory — keep it separate from ExportPreviewLoaded so the domain state remains decoupled from the wire format. The mapper class (ExportPreviewMapper) is the only place that knows about both the DTO and the domain state; keep it a static utility class with no dependencies for easy testing. Use the supabase_flutter client's built-in auth header injection rather than manually setting headers. For the Edge Function, ensure the request body includes a 'preview: true' flag that the server checks before any write.

Log error status codes at the warning level using the app's logger; never log response bodies as they may contain PII.

Testing Requirements

Unit tests for ExportPreviewMapper covering: full happy-path DTO with categories, scope, period, warnings; empty categories list; null unitId; malformed DTO triggering MappingException. Integration test for BufdirExportService using a mock HTTP client (http_mock_adapter or similar) simulating 200, 403, 500, and timeout scenarios. Verify no audit log rows are created by checking the mock Supabase client's recorded write calls. Use flutter_test and mocktail for all mocks.

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.