high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

BufdirExportHistoryList renders a ListView.builder of ExportHistoryListItem widgets using data from the Riverpod provider
Each ExportHistoryListItem displays: reporting period label, export format badge (e.g. 'CSV'), status chip (green 'Completed' or red 'Failed'), human-readable timestamp (e.g. '23 Mar 2026, 14:05'), and a Re-download icon button
Re-download button is only enabled (tappable) when item status is 'completed' and fileUrl is non-null
Re-download button is visually disabled and has Semantics label 'Not available' when status is failed or fileUrl is null
Infinite scroll: when user scrolls within 3 items of the end of the list, loadNextPage() is called on the provider
A loading spinner is shown at the bottom of the list while the next page loads
No duplicate loadNextPage() calls are made while a page load is already in progress
Pull-to-refresh (RefreshIndicator) calls provider's refresh() method, resets to page 1, and shows a spinner
Empty state screen is shown when provider returns an empty list — contains an illustration, 'No exports yet' heading, and 'Export data' call-to-action button that navigates to ExportTriggerScreen
Error state shows an inline error card with a 'Try again' button that retries the last fetch
All list items have Semantics labels covering period, format, status, and timestamp for screen reader users
List renders correctly on screen widths from 360dp to 428dp without overflow

Technical Requirements

frameworks
Flutter
Riverpod
data models
bufdir_export_audit_log
performance requirements
ListView.builder must be used — never ListView with all items materialised upfront
ExportHistoryListItem must be a const-constructible widget where possible to minimise rebuilds
ScrollController listener for infinite scroll must be debounced — do not call loadNextPage on every scroll event
security requirements
File URLs from history items must not be rendered as tappable links directly — always route through the re-download flow that validates URL freshness
ui components
BufdirExportHistoryList (screen)
ExportHistoryListItem (list item widget)
ExportFormatBadge
ExportStatusChip
RefreshIndicator
CircularProgressIndicator (pagination spinner)
BufdirExportHistoryEmptyState
ScrollController

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use ConsumerWidget with ref.watch(exportHistoryProvider) to drive the list. Implement infinite scroll via ScrollController: attach a listener that checks `controller.position.pixels >= controller.position.maxScrollExtent - threshold` and calls loadNextPage only when hasMore is true and no load is in progress. Use a ValueNotifier isLoadingMore inside the widget to prevent duplicate calls — set to true before loadNextPage and false in the provider's state change callback. For the empty state, use an SVG or PNG illustration from the app's asset bundle — do not use Icon widgets as the primary visual.

Format timestamps using the intl package with a locale-aware DateFormat. Status chip colours must use the app's design token system, not hardcoded hex values.

Testing Requirements

Write widget tests with flutter_test covering: (1) list renders correct number of ExportHistoryListItem widgets from mocked provider data, (2) each item displays correct period, format, status, and timestamp, (3) Re-download button is disabled for failed items and enabled for completed items, (4) scroll to near-end triggers loadNextPage, (5) pull-to-refresh calls refresh(), (6) empty state renders when provider returns empty list, (7) error state renders with retry button. Mock Riverpod provider using ProviderScope overrides with a fake ExportHistoryNotifier. Include a golden test for ExportHistoryListItem in completed and failed states.

Component
Export History List
ui medium
Epic Risks (2)
medium impact medium prob technical

For large exports that run for 10–30 seconds, a static loading spinner will feel broken to users on slow mobile connections. If the UI cannot display meaningful progress during the export pipeline, coordinators may abandon the flow or trigger duplicate exports by pressing the button multiple times.

Mitigation & Contingency

Mitigation: Implement streaming progress events from the orchestrator BLoC through named pipeline stages (querying, mapping, generating, uploading). Display each stage label with a progress indicator on the trigger screen. Disable the generate button immediately on first tap to prevent duplicates.

Contingency: If streaming pipeline progress is not feasible in the first release, implement a deterministic stage-based progress animation (10% querying, 50% generating, 90% uploading) that gives users feedback without requiring real server events.

high impact medium prob technical

Custom date range pickers are among the most common accessibility failures in mobile apps. Blindeforbundet users rely on VoiceOver, and NHF users include people with cognitive impairments. A non-accessible period picker could make the entire export workflow unusable for a significant portion of the intended user base.

Mitigation & Contingency

Mitigation: Build the period picker using Flutter's native date picker semantics as the foundation, with preset shortcuts as primary navigation (reducing the need to interact with the custom range picker at all). Test with VoiceOver on iOS and TalkBack on Android before UI epic sign-off. Engage Blindeforbundet's test contact for accessibility validation.

Contingency: If the custom date range picker cannot be made fully accessible before release, ship only the preset period shortcuts (covering the majority of use cases) and add the custom range picker in a follow-up sprint after dedicated accessibility remediation.