high priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

ExportTriggerState is a sealed class with subtypes: ExportTriggerIdle, ExportTriggerPeriodSelected, ExportTriggerFormatSelected, ExportTriggerConfirming, ExportTriggerExporting (int progress 0–100), ExportTriggerComplete (String fileUrl), ExportTriggerError (String message)
ExportTriggerEvent is a sealed class with subtypes: PeriodSelectedEvent(DateTimeRange), FormatSelectedEvent(BufdirExportFormat), ConfirmRequestedEvent, ConfirmCancelledEvent, ExportStartedEvent, ExportProgressUpdatedEvent(int progress), ExportCompletedEvent(String fileUrl), ExportFailedEvent(String error)
State machine enforces valid transitions: idle → periodSelected on PeriodSelectedEvent; periodSelected + formatSelected → confirming on ConfirmRequestedEvent; confirming → exporting on ExportStartedEvent; exporting → complete/error on terminal events; any state → idle on reset event
Invalid event/state combinations are ignored (no exception thrown) and a debug assertion logs a warning
ExportTriggerCubit/Bloc is provided via a Riverpod Provider (or AutoDisposeProvider) named exportTriggerBlocProvider
The Bloc holds the current period and format in state so the confirmation dialog can read them without prop-drilling
exportingProgress is clamped to 0–100 and emitted as an int — downstream widgets must not assume float
Unit tests achieve 100% state-transition branch coverage using bloc_test package
Riverpod provider is scoped to the ExportTriggerScreen route and auto-disposes on pop

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
Bufdir Export REST API (called from Bloc via repository abstraction)
data models
ExportTriggerState (sealed)
ExportTriggerEvent (sealed)
DateTimeRange
BufdirExportFormat
BufdirExportRepository (abstract interface injected into Bloc)
performance requirements
State transitions are synchronous except ExportStartedEvent which triggers an async repository call
Progress stream from repository is piped into Bloc without buffering to ensure real-time UI updates
security requirements
Export API call must include the authenticated Supabase user JWT as a Bearer token
fileUrl returned on completion must be a signed Supabase Storage URL with a short TTL (e.g., 5 minutes) — Bloc must not cache it beyond exportComplete state lifetime
ExportFailedEvent message must not expose raw server error details to the UI — map to user-friendly strings in the Bloc

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use flutter_bloc Cubit (not full Bloc) if event handling is straightforward — Cubit reduces boilerplate while still being testable with bloc_test. Define ExportTriggerState as a sealed class in Dart 3 using the sealed keyword; each subtype is a final class with only the fields it needs (e.g., ExportTriggerExporting only carries progress: int). Inject BufdirExportRepository as an abstract interface into the Cubit constructor — this makes unit testing trivial via fakes without needing http mocking. Wire the Riverpod provider using riverpod_annotation (@riverpod) for type-safe code generation, scoped to AutoDispose to prevent memory leaks.

The progress stream from the export API should be consumed with await for inside the Cubit's async method, emitting ExportTriggerExporting(progress: p) for each progress tick. Map all repository exceptions to user-friendly error strings in a private _mapError(Object e) method inside the Cubit — never pass raw exception messages to ExportFailedEvent.

Testing Requirements

Use the bloc_test package to write exhaustive state-transition tests: (1) happy path from idle → complete with progress updates at 0, 50, 100, (2) error path: ExportFailedEvent emits ExportTriggerError with sanitized message, (3) ConfirmCancelledEvent from confirming returns to formatSelected state with period and format preserved, (4) invalid transitions (e.g., PeriodSelectedEvent while exporting) do not change state, (5) exportingProgress is always 0–100 (test clamping at boundaries), (6) Riverpod provider auto-disposes when screen is popped (use ProviderContainer in test). Mock BufdirExportRepository using Mockito or manual fake. Target 100% line + branch coverage on Bloc logic.

Component
Bufdir Export Trigger Screen
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.