critical priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

BLoC is implemented using the bloc package with typed event and state classes (sealed classes or abstract base with subtypes)
All 7 states are implemented as immutable classes: Idle, LoadingConfig, ConfigLoaded, Exporting, ExportSuccess, ExportError, DuplicateWarning
All 5 events are implemented: LoadConfig, InitiateExport (carries DateTimeRange), ConfirmExport, CancelExport, DownloadFile
On LoadConfig event: BLoC emits LoadingConfig, fetches org config from repository, then emits ConfigLoaded(exporterType, orgName) or ExportError if fetch fails
On InitiateExport event: BLoC calls the Double-Export Guard with the provided date range; if duplicate detected emits DuplicateWarning(existingRunId); otherwise emits Exporting and proceeds with export
On ConfirmExport event (override path): BLoC skips the duplicate guard and proceeds directly to export, emitting Exporting then ExportSuccess or ExportError
On CancelExport event from DuplicateWarning or Exporting state: BLoC returns to ConfigLoaded state preserving the loaded config
ExportSuccess state carries exportRunId and fileUrl for the download action
DuplicateWarning state carries existingRunId so the UI can display the conflicting export's reference
BLoC does not emit states synchronously — all transitions go through the event handler (no direct calls to emit outside mapEventToState / on<Event>)
BLoC closes cleanly without stream leaks: all subscriptions cancelled in close() override
BLoC is registered via BlocProvider scoped to the AccountingExportScreen route

Technical Requirements

frameworks
Flutter
BLoC (bloc ^8.x)
Riverpod (for repository injection into BLoC)
apis
Export Edge Function client (task-005)
Double-Export Guard service
OrgConfigRepository.getExporterConfig(orgId)
data models
annual_summary
bufdir_export_audit_log
claim_event
performance requirements
State transitions must complete within 200ms for local guard checks
BLoC must not block the UI thread — all async work performed in event handlers with await
No redundant state emissions — use emit only when state actually changes
security requirements
BLoC must read orgId from the authenticated session JWT claims (via Supabase Auth provider) — never accept orgId as a constructor parameter from untrusted UI input
Export confirmation must verify the user's role is coordinator or org_admin before dispatching to the Edge Function — emit ExportError with 'Unauthorized' if role check fails

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use sealed classes (Dart 3.0+) for both events and states to enable exhaustive pattern matching in the UI. Define a single abstract AccountingExportState base and AccountingExportEvent base. Inject repository dependencies via the BLoC constructor (not via Riverpod inside the BLoC) to keep the BLoC testable without the Riverpod container. The Double-Export Guard should be a separate class (not inlined in BLoC) that accepts a date range and org ID and returns a DuplicateCheckResult.

Implement CancelExport by storing the last-known ConfigLoaded state as a field so it can be re-emitted on cancel without re-fetching. Avoid using BLoC transformers for this use-case — sequential event processing (default) is correct here since export is a sequential operation.

Testing Requirements

Unit tests using bloc_test package: test each event→state transition in isolation with mocked repositories. Specifically test: LoadConfig success path (Idle→LoadingConfig→ConfigLoaded); LoadConfig failure path (Idle→LoadingConfig→ExportError); InitiateExport with duplicate detected (ConfigLoaded→DuplicateWarning); InitiateExport without duplicate (ConfigLoaded→Exporting→ExportSuccess); ConfirmExport override path skips guard; CancelExport from DuplicateWarning returns to ConfigLoaded. Use mocktail or mockito to mock the Export Edge Function client and Double-Export Guard. Verify BLoC emits states in the correct order using blocTest's 'expect' list.

Verify BLoC.close() cancels all stream subscriptions. Target 95%+ coverage on BLoC logic.

Component
Accounting Export Screen
ui medium
Epic Risks (2)
medium impact medium prob technical

Export operations may take several seconds, and the UI must handle all intermediate states (loading, partial success, failure, duplicate warning) without leaving the coordinator on a blank or unresponsive screen. Missing state handling causes confusion and potentially double-submissions.

Mitigation & Contingency

Mitigation: Design the BLoC state machine with explicit states for each transition before writing any widget code: ExportIdle, ExportDuplicateWarning, ExportInProgress, ExportSuccess, ExportPartialSuccess, ExportFailed. Each state maps to a distinct UI. Widget tests cover all states.

Contingency: If a loading state is missed in production, surface a generic error state with a retry action rather than leaving the UI stuck. Add a timeout on the Edge Function call (default 30 seconds) that transitions to ExportFailed with a user-readable message.

high impact medium prob technical

The custom Export Date Range Picker may not be fully navigable with VoiceOver if the underlying Flutter date widgets do not expose the correct semantic tree. This is a critical accessibility failure for Blindeforbundet users who rely on screen readers.

Mitigation & Contingency

Mitigation: Use Flutter's built-in DateRangePicker as the base and wrap with explicit Semantics nodes for start and end labels. Test with VoiceOver on a physical iOS device as part of the definition of done for this component. Reference the existing AccessibilityTestHarness pattern used elsewhere in the app.

Contingency: If the custom picker fails accessibility audit, replace it with two independent DatePicker fields (start and end) using Flutter's standard accessible date input, which has broader VoiceOver support than range variants.