high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Panel displays a scrollable list of ExportRun records for the authenticated org, ordered by created_at descending (most recent first)
Each list item shows: export run date (formatted 'dd MMM yyyy'), date range (e.g., 'Jan 2025 – Jan 2025'), TargetSystemBadge (Xledger/Dynamics), StatusBadge (pending/completed/failed with distinct colors from design tokens), record count (e.g., '14 records'), and a Download button
Download button is visible only for runs with status 'completed' and a non-null file_url; it is hidden for pending/failed runs
Tapping Download: calls ExportRunRepository.getSignedUrl(runId) to fetch a fresh signed URL, then calls url_launcher launchUrl() to open the file in the native handler
While the signed URL is being fetched, the Download button shows a CircularProgressIndicator and is disabled
If url_launcher cannot open the URL, a SnackBar error is shown: 'Could not open file. Please try again.'
Loading state: displays 3 shimmer skeleton list items (using shimmer package or equivalent design token skeleton)
Empty state: displays an icon and message 'No exports yet' with a subtitle 'Completed exports will appear here'
Error state: displays an error message with a Retry button that calls refreshExportHistory()
Panel is driven by a Riverpod provider (exportHistoryProvider) that wraps ExportRunRepository.getExportRunsForOrg(orgId)
All list items have Semantics labels readable by screen readers including status and record count
Widget tests cover: loading state, empty state, error state, completed run renders download button, pending run hides download button

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
Supabase Storage — signed URL fetch via ExportRunRepository.getSignedUrl()
url_launcher — open native file handler
performance requirements
List renders up to 50 export runs without jank — use ListView.builder for lazy rendering
Signed URL fetch completes within 500ms; show loading indicator if exceeded
security requirements
Signed URL fetched on demand (not stored) — prevents stale URL usage after 1-hour expiry
Panel only displays export runs for the authenticated user's organization — orgId sourced from JWT claims via Riverpod auth provider, never from widget parameters
Download button disabled for non-completed runs to prevent access to partial or failed export files
ui components
ExportRunListItem — list tile with all 6 data fields + download button
TargetSystemBadge — reusable badge (shared with task-008)
StatusBadge — colored badge: pending (amber), completed (green), failed (red) using design tokens
SkeletonListItem — shimmer placeholder matching ExportRunListItem dimensions
EmptyExportHistoryState — icon + message widget for zero-records case

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use Riverpod's AsyncNotifierProvider for exportHistoryProvider — it gives clean loading/error/data states without manual StreamController management. The provider should call ExportRunRepository.getExportRunsForOrg(orgId) once and cache; expose a refresh() method on the notifier for the Retry button. List item: use Flutter's ListTile or a custom card — match the design system card style. TargetSystemBadge and StatusBadge should be extracted as shared widgets usable from the confirmation dialog (task-008) as well.

For the shimmer effect: if the shimmer package is already in pubspec, use it; otherwise implement a simple animated opacity pulse with AnimationController — do not add a new dependency solely for this. Download flow: use a local bool _isDownloading per list item (or a Set of downloading run IDs in the notifier) to track loading state per item rather than a global flag. Use url_launcher's canLaunchUrl before launchUrl and show the SnackBar error if false. Format dates with intl DateFormat('dd MMM yyyy', 'nb_NO') for Norwegian locale consistency.

Testing Requirements

Widget tests (flutter_test): use ProviderScope with overridden exportHistoryProvider; test loading state renders 3 skeleton items; test empty state renders correct message; test error state renders Retry button; test completed run renders Download button; test pending run hides Download button; test tapping Download triggers getSignedUrl and launchUrl. Mock ExportRunRepository and url_launcher in tests. Accessibility test: verify Semantics labels on status badges and download buttons. No golden tests required for this task.

Test file: test/widgets/export_history_panel_test.dart.

Component
Export Run Repository
data medium
Epic Risks (3)
high impact medium prob technical

Adding exported_at and export_run_id columns to expense_claims requires a live migration on a table shared with the approval workflow. A poorly timed migration could lock the table and block claim submissions or approvals.

Mitigation & Contingency

Mitigation: Use non-blocking ADD COLUMN with a DEFAULT of NULL (no backfill needed) executed during a low-traffic window. Test migration rollback on a staging replica before production deployment.

Contingency: If migration causes table lock contention, roll back and reschedule for a maintenance window. Use a feature flag to gate the export UI until the migration completes successfully.

medium impact high prob scope

Chart of accounts mapping configurations for Xledger and Dynamics may not be fully specified by stakeholders at development time, leaving the mapper with incomplete data and causing validation failures for unmapped expense categories.

Mitigation & Contingency

Mitigation: Implement the mapper to return a structured validation error (not a crash) for any unmapped field, and surface these errors clearly in the export confirmation dialog. Request full mapping tables from Blindeforbundet and HLF stakeholders as a pre-condition for this epic.

Contingency: If mappings arrive incomplete, ship the mapper with the available subset and mark unmapped categories as excluded (skipped with reason). Coordinators see which categories are skipped and can manually submit those records.

medium impact medium prob dependency

Supabase Vault configuration for storing per-org accounting credentials may require infra permissions or environment secrets not yet provisioned in staging or production, blocking development and testing of credential retrieval.

Mitigation & Contingency

Mitigation: Provision Vault secrets and environment configuration in staging as the first task of this epic. Document the exact secret naming convention and rotation procedure before implementation begins.

Contingency: If Vault is unavailable, use environment variables scoped to the Edge Function as a temporary fallback for development. Block production deployment until Vault-based storage is confirmed operational.