medium priority low complexity frontend pending frontend specialist Tier 8

Acceptance Criteria

ExportHistoryList widget renders a scrollable list of past exports fetched from the generated_reports repository via the edge function or Supabase client
List is ordered by created_at descending (most recent first)
Each list item displays: formatted export date (dd.MM.yyyy), scope label (chapter name or 'Region' or 'National'), period range (e.g., 'Q1 2025'), format badge styled differently for CSV vs PDF, file size formatted as human-readable (e.g., '42 KB'), and a re-download icon button
Format badge for CSV uses a distinct color from PDF badge (e.g., green for CSV, blue for PDF) consistent with the app's design token system
Re-download button shows a loading indicator while the signed URL is being fetched
After signed URL is fetched, the file download is triggered using url_launcher or equivalent Flutter mechanism to open the URL in the browser/OS handler
If re-download API call fails, a snackbar error message is shown: 'Could not generate download link. Please try again.'
If the export record has status 'failed', the list item shows a 'Failed' status chip instead of the re-download button
Empty state is shown when the list is empty: an illustration with text 'No exports yet. Generate your first Bufdir export above.'
List shows a loading skeleton while data is being fetched
Widget is accessible: all interactive elements have semantic labels for screen readers (VoiceOver/TalkBack)
Widget handles pagination or load-more if more than 20 records exist — initial load is limited to 20 items
Widget refreshes the list after a new export is successfully generated (parent notifies via callback or state management)

Technical Requirements

frameworks
Flutter
BLoC or Riverpod (consistent with existing state management in the app)
url_launcher (for opening download URLs)
apis
Supabase client (listByOrg query on generated_reports)
Bufdir Export Edge Function re-download endpoint
data models
bufdir_export_audit_log
performance requirements
Initial list load must complete within 2 seconds on a 4G connection
Skeleton placeholders shown within 16ms of widget mount — no layout shift
Re-download URL fetch must show loading state immediately on button tap
List scroll performance must maintain 60fps — use const constructors and avoid rebuilding list items on unrelated state changes
security requirements
Signed download URL is opened via url_launcher — not stored or logged in the Flutter app
No file_path or internal storage paths are rendered or stored client-side
List fetch uses user JWT automatically via Supabase client — no manual auth token handling
WCAG 2.2 AA: contrast ratio >= 4.5:1 for all text elements; re-download button target size >= 44x44dp
ui components
ExportHistoryList (StatelessWidget with BLoC/Riverpod consumer)
ExportHistoryListItem (list tile with date, scope, period, format badge, file size, re-download button)
FormatBadge (small styled chip for CSV/PDF)
ExportHistoryEmptyState (empty state illustration + message)
ExportHistoryLoadingSkeleton (shimmer placeholder rows)

Execution Context

Execution Tier
Tier 8

Tier 8 - 48 tasks

Can start after Tier 7 completes

Implementation Notes

Use the existing design token system for all colors, typography, and spacing — do not hardcode values. The format badge color should use tokens (e.g., `AppColors.csvBadge` and `AppColors.pdfBadge`) rather than literals. Fetch the export list using the existing Supabase client instance from the app's dependency injection — do not create a new client. For re-download, call the edge function via the existing HTTP client wrapper used elsewhere in the app.

Use url_launcher's `launchUrl` with `LaunchMode.externalApplication` to open the signed URL so the OS handles the file download appropriately. Scope label formatting: for scope_level=chapter, display the scope_id resolved to a human-readable chapter name (query from existing chapter data in state); for region/national, use localised string constants. Period range formatting: derive readable label from period_from and period_to dates (e.g., 'Q1 2025' for Jan-Mar). Keep the widget stateless and manage all async state (loading, error, data) in a dedicated BLoC/Cubit or Riverpod notifier — do not use StatefulWidget for this.

Testing Requirements

Widget tests: render ExportHistoryList with a mock list of 3 exports and assert each item displays correct date, scope, format badge, and file size. Test that tapping re-download button triggers the re-download callback and shows loading indicator. Test empty state renders when list is empty. Test that a failed-status export shows 'Failed' chip and no re-download button.

Test accessibility: use flutter_test's SemanticsController to verify semantic labels on interactive elements. Integration test using a Supabase test project: fetch real records and assert they render in descending order. Test error handling: mock the re-download API to return 422 and assert the snackbar message appears. Verify VoiceOver compatibility manually on iOS device or simulator during QA.

Component
Export History List
ui low
Epic Risks (3)
high impact medium prob technical

Supabase Edge Functions have a default execution timeout. For large national-scope exports aggregating tens of thousands of activities across 1,400 chapters, the edge function may time out before completing, leaving coordinators with a failed export and no partial output.

Mitigation & Contingency

Mitigation: Optimise the aggregation SQL using pre-materialised aggregation views or RPC functions that run inside the database rather than iterating records in Deno. Profile query execution time against realistic production data volumes early. Request an elevated timeout limit from Supabase if needed. Implement progress checkpointing so the export can be resumed from the last completed aggregation batch.

Contingency: For organisations exceeding a configurable threshold (e.g. >5,000 activities), switch to an asynchronous export pattern: the edge function writes a 'pending' audit record and enqueues the job; the client polls for completion and is notified via Supabase Realtime when the file is ready.

medium impact medium prob technical

Server-side PDF generation in a Deno Edge Function environment restricts library choices. Many popular PDF libraries require Node.js APIs not available in Deno, or produce large bundle sizes that exceed edge function limits. Choosing the wrong library could block the entire PDF generation path.

Mitigation & Contingency

Mitigation: Spike PDF library selection as the first task of this epic, evaluating at least two Deno-compatible options (e.g. pdf-lib, jsPDF with Deno compatibility shim). Test bundle size and basic rendering before committing to an implementation. Document the chosen library's constraints.

Contingency: If no suitable Deno-native PDF library is found, generate a well-structured HTML report from the edge function and use a headless Chromium service (e.g. Browserless, Gotenberg) for HTML-to-PDF conversion, or temporarily ship CSV-only export while the PDF path is resolved.

high impact high prob technical

Peer mentors affiliated with multiple chapters (a documented NHF scenario) must not be double-counted in participant totals. Incorrect deduplication logic would overreport participation figures to Bufdir, which could be discovered during audit and damage organisational credibility.

Mitigation & Contingency

Mitigation: Define and document the deduplication contract explicitly before coding: deduplication is per-person per-period, not per-activity. Build dedicated unit tests with fixtures containing the exact multi-chapter membership patterns described in NHF's documentation. Have a NHF representative validate test fixture outputs against known-good manual counts.

Contingency: If deduplication logic produces results that cannot be verified against manual counts before launch, surface a deduplication warning in the export preview listing the affected peer mentor IDs, and require explicit coordinator acknowledgement before finalising the export.