high priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

ExportHistoryRepository exposes a fetchPage(int page, int pageSize) method returning Future<List<ExportHistoryEntry>>
Repository queries Supabase `bufdir_export_audit_log` table with `.eq('organization_id', orgId)` scoping — never fetches cross-org records
ExportHistoryEntry model contains: id (String), reportPeriodId (String), format (String), status (ExportStatus enum: completed/failed/pending), createdAt (DateTime), fileUrl (String?)
fileUrl is nullable — null for failed or pending exports
Riverpod AsyncNotifierProvider<ExportHistoryNotifier, List<ExportHistoryEntry>> exposes loading, data, and error states
Provider supports pagination: initial load fetches page 1, loadNextPage() appends subsequent pages
Provider exposes a hasMore boolean that becomes false when a page returns fewer items than pageSize
RLS on the Supabase table enforces organisation scope server-side — client-side .eq filter is defence-in-depth only
On Supabase error, provider transitions to AsyncError state with a typed ExportHistoryException
Repository has a corresponding abstract interface (ExportHistoryRepositoryInterface) to enable mock injection in tests
Unit tests verify pagination logic, empty-page detection, and error propagation with a mocked Supabase client

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase PostgreSQL 15 (select from bufdir_export_audit_log)
Supabase Auth (JWT org_id claim extraction)
data models
bufdir_export_audit_log
performance requirements
Initial page load must complete within 2 seconds on a standard 4G connection
Page size default of 20 items balances payload size and scroll distance
Query must use indexed columns (organization_id, created_at DESC) — confirm index exists in schema
security requirements
organisation_id must be read from the authenticated Supabase JWT claims, never from a client-provided parameter
Row-Level Security policy on bufdir_export_audit_log must restrict SELECT to rows matching auth.jwt()->>'organization_id'
Service role key must never be used from the mobile client — all queries use the anon key with RLS

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Supabase's `.from('bufdir_export_audit_log').select().eq('organization_id', orgId).order('created_at', ascending: false).range(from, to)` for pagination. Extract orgId from `Supabase.instance.client.auth.currentSession?.user.userMetadata?['organization_id']` — validate it is non-null before querying and throw an AuthenticationException if absent. Model ExportStatus as a Dart enum with a fromString factory that defaults to ExportStatus.pending for unknown values to prevent runtime crashes from new server-side statuses. Keep the repository thin — no business logic, only data fetching and mapping.

The Riverpod notifier handles pagination state (currentPage, allItems, hasMore). Use AsyncNotifier rather than StateNotifier for cleaner async loading patterns.

Testing Requirements

Write unit tests with flutter_test and mockito/mocktail covering: (1) fetchPage returns correct ExportHistoryEntry list from mocked Supabase response, (2) fetchPage with empty response sets hasMore to false, (3) loadNextPage appends items correctly to existing list, (4) Supabase exception maps to ExportHistoryException and provider enters AsyncError, (5) organisation_id is correctly extracted from auth session and appended to query. Test ExportHistoryNotifier state transitions with ProviderContainer. Do not hit a real Supabase instance in unit tests.

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.