critical priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Navigating to the Preview phase immediately calls ExportPreviewCubit.fetchPreview() with the scope and period from Riverpod providers
ExportProgressIndicator is displayed from the moment fetchPreview() is called until the fetch settles
ExportPreviewPanel renders category rows with record counts and totals when the fetch succeeds
A blocking error state is shown with a descriptive message when the preview fetch fails
'Back to Configuration' button is always visible in the Preview phase and returns the user to phase one
Tapping 'Back to Configuration' calls ExportPreviewCubit.resetPreview() clearing all fetched data and error state
'Confirm Export' button is visible only when ExportPreviewCubit state is PreviewLoaded with hasBlockingErrors == false
'Confirm Export' button is hidden (not just disabled) when preview has blocking errors or is still loading
The Preview phase does not re-fetch if the user navigates back and forward without changing scope or period (cache hit within the same screen session)
Focus is moved to the first element of ExportPreviewPanel after loading completes, announced by screen readers

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
Riverpod
apis
Bufdir Preview REST API (GET /bufdir/preview?scope=&period=)
data models
ExportPreviewResult
ExportPreviewCategory
ExportScreenPhase
performance requirements
Preview fetch must time out after 30 seconds and surface a user-friendly error
ExportPreviewPanel must render with no jank for up to 50 category rows
security requirements
Preview API request must include the authenticated Supabase JWT in the Authorization header
Preview response must not be cached to disk — in-memory only for the screen session
ui components
ExportPreviewPanel
ExportProgressIndicator
'Back to Configuration' TextButton
'Confirm Export' primary action button (conditionally visible)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Model the preview fetch lifecycle as ExportPreviewState variants: PreviewIdle, PreviewLoading, PreviewLoaded(data, hasBlockingErrors), PreviewError(message). Use BlocBuilder with buildWhen to avoid rebuilding the entire phase widget on unrelated cubit emissions. Cache the last successful ExportPreviewResult inside the cubit (not the repository) and return it immediately on re-entry if scope and period match the cached query key. The 'Confirm Export' button visibility should use an Offstage or conditional rendering — never just opacity: 0 — to prevent screen readers from discovering a hidden interactive element.

Testing Requirements

Widget tests: (1) entering Preview phase triggers fetchPreview() on the cubit — verify via bloc_test emitted states, (2) loading state shows ExportProgressIndicator and hides 'Confirm Export', (3) loaded state with no blocking errors shows 'Confirm Export', (4) loaded state with blocking errors hides 'Confirm Export', (5) error state shows error message and no 'Confirm Export', (6) 'Back to Configuration' always visible and calls resetPreview() on tap, (7) second entry to Preview phase without scope/period change does not call fetchPreview() again. Use bloc_test and mocktail.

Component
Bufdir Export Screen
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.