high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Widget renders three preset options as tappable chips or segmented control: 'Current month', 'Last month', 'Custom range' — all labels localised
Selecting 'Current month' sets DateTimeRange to the first and last day of the current calendar month and immediately fires the onRangeSelected callback
Selecting 'Last month' sets DateTimeRange to the first and last day of the previous calendar month and immediately fires the onRangeSelected callback
Selecting 'Custom range' opens a date range picker dialog (Flutter's showDateRangePicker or equivalent) allowing arbitrary start and end date selection
If custom range start date is after end date, an inline validation error is shown and onRangeSelected is NOT called
onRangeSelected callback is of type void Function(DateTimeRange) and is called exactly once per valid selection
All interactive elements have Semantics labels readable by VoiceOver (iOS) and TalkBack (Android) — verified with Flutter's SemanticsDebugger
All text and interactive elements meet WCAG 2.2 AA contrast ratio (minimum 4.5:1 for normal text, 3:1 for large text) — verified against the app's design token color system
Widget is fully navigable via keyboard on platforms that support it (Flutter desktop/web) using Tab, Enter, and arrow keys
Widget does not use any inline styles — all colors and typography come from the app's design token system (ThemeData / AppTokens)
Widget is stateless externally — caller controls the selected range via an optional initialRange parameter; internal selection state is managed within the widget
flutter_test widget tests pass for all acceptance scenarios above on both iOS and Android configurations

Technical Requirements

frameworks
Flutter (latest stable)
flutter_test
BLoC or Riverpod for state if needed at parent level
performance requirements
Widget must render within one frame (16ms) with no jank on initial display
Date range computation for presets must be synchronous — no async calls
Custom range dialog must open in under 100ms from tap
security requirements
Widget must not accept date ranges that extend beyond the current date — future dates are invalid for expense exports
Date range must be validated before the callback fires to prevent invalid data reaching the API layer
ui components
ExportDateRangePicker (this widget)
Preset selector (SegmentedButton or FilterChip row)
Flutter built-in showDateRangePicker dialog for custom range
Inline validation error text widget (using AppTokens error color)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Flutter's built-in `showDateRangePicker()` for the custom range dialog — do not build a custom calendar from scratch. Wrap all interactive elements with `Semantics(label: ..., button: true)` widgets and use `ExcludeSemantics` for decorative elements. For preset computation, use the Dart `DateTime` class with UTC normalization to avoid DST edge cases: `DateTime.utc(now.year, now.month, 1)` for month start and `DateTime.utc(now.year, now.month + 1, 0)` for month end. Expose the widget's design through a clean API: `ExportDateRangePicker({Key?

key, DateTimeRange? initialRange, required void Function(DateTimeRange) onRangeSelected})`. Store preset selection state using a local `_selectedPreset` enum (`currentMonth`, `lastMonth`, `custom`) to drive the UI clearly. Use the app's existing AppTokens for colors — never hardcode hex values.

If BLoC is used at the parent screen level, emit an ExportRangeSelected event from the callback rather than calling a BLoC method directly from within this widget — keep the widget decoupled.

Testing Requirements

Write flutter_test widget tests covering: (1) renders all three preset options; (2) tapping 'Current month' fires callback with correct DateTimeRange for this month; (3) tapping 'Last month' fires callback with correct DateTimeRange for last month; (4) tapping 'Custom range' opens date picker dialog; (5) selecting valid custom range fires callback; (6) selecting custom range with start after end shows validation error and does NOT fire callback; (7) initialRange parameter pre-selects the matching preset or shows custom range with dates pre-filled; (8) Semantics test — use tester.getSemantics() to assert that all interactive elements have non-empty label properties; (9) contrast test — assert that all text colors from design tokens meet 4.5:1 ratio against their background tokens. Run tests with `flutter test` targeting both iOS and Android configurations.

Component
Export Date Range Picker
ui low
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.