high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

A 'Custom range' button is present alongside the preset buttons from task-001; tapping it opens a modal calendar dialog
The calendar dialog allows the user to select a start date and an end date independently, in that order, with clear visual indication of which step is active
If the user selects an end date that is before or equal to the start date, a plain-language error message is displayed inline (e.g., 'End date must be after start date') — the Confirm button remains disabled
On successful selection the dialog closes and BufdirPeriodSelectorWidget invokes onPeriodSelected with the custom DateTimeRange, clearing any active preset selection
Date changes are announced via Semantics.liveRegion (or equivalent) so VoiceOver (iOS) and TalkBack (Android) read the newly selected date aloud without requiring refocus
The dialog is dismissible via the Cancel button, back-navigation gesture, or tapping outside — in all cases no callback is fired and previous selection is preserved
Minimum selectable date is Jan 1 of current year minus 5 years; maximum is today, preventing future-period exports
Dialog renders correctly at font scale 2.0× without layout overflow
Calendar is navigable using switch-access and keyboard (when connected) with correct focus traversal order
Unit and widget tests cover validation logic and callback behavior

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
data models
DateTimeRange
BufdirReportingPeriod
CustomPeriodState
performance requirements
Dialog open animation must complete within 300 ms on mid-range Android devices
Date arithmetic runs synchronously; no futures in validation path
security requirements
User-supplied dates must be validated server-side before export API call — widget validation is UX only, not a security boundary
No PII in widget state
ui components
BufdirCustomDateRangePickerDialog (modal dialog)
'Custom range' trigger button extending PresetPeriodButton style
Inline validation error text widget
Semantics.liveRegion announcement nodes

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use Flutter's built-in showDateRangePicker or a two-step showDatePicker sequence — evaluate which gives better screen-reader UX on both platforms. If using showDateRangePicker, wrap it in a helper that posts a live-region Semantics announcement after the picker resolves. For the validation error, use a ValueNotifier inside the dialog to avoid rebuilding the entire widget tree. The error string should be defined as a constant (or come from a l10n key) to facilitate future localization.

Ensure the 'Custom range' button uses the same PresetPeriodButton widget from task-001 with an isCustom flag that renders a calendar icon instead of a text date. Keep validation logic in a pure function (isValidDateRange) so it can be unit-tested without Flutter dependencies. Connect back to BufdirPeriodSelectorWidget via the existing onPeriodSelected callback — no new callback interface needed.

Testing Requirements

Write flutter_test widget tests: (1) tapping 'Custom range' opens dialog, (2) selecting end before start shows error message and disables Confirm, (3) selecting valid range closes dialog and fires onPeriodSelected with correct DateTimeRange, (4) Cancel closes dialog without firing callback, (5) previous preset selection is preserved on Cancel, (6) liveRegion Semantics node exists and updates on date change, (7) future dates are not selectable (Confirm disabled). Write unit tests for validation pure function: isValidRange(start, end) → true/false with edge cases (same day = invalid, start+1day = valid). Target 100% branch coverage on validation logic.

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.