critical priority low complexity frontend pending frontend specialist Tier 7

Acceptance Criteria

BlocListener<PeriodSelectionBloc, PeriodSelectionState> is added above or wrapping the BlocBuilder, not replacing it
When listener receives PeriodConfirmedState, it calls ref.read(confirmedReportPeriodProvider.notifier).state = state.confirmedRange exactly once
After updating the provider, listener calls context.push('/bufdir/export') (or equivalent router method) to navigate forward
Navigation to export flow does not occur if state is not PeriodConfirmedState — listener ignores all other states
AppBar back button or system back gesture dispatches ResetPeriod event to the BLoC before popping the route
After ResetPeriod is dispatched, the BLoC returns to its initial state so re-entering the screen starts clean
confirmedReportPeriodProvider is a StateProvider<DateTimeRange?> (or equivalent Riverpod notifier) and the write operation does not throw
No navigation occurs more than once per PeriodConfirmedState emission (guard against duplicate listener calls)
Widget test verifies provider is updated before navigation is triggered

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
data models
confirmedReportPeriodProvider
DateTimeRange
PeriodConfirmedState
ResetPeriod event
performance requirements
Provider write and navigation are synchronous — no async gaps between them to avoid race conditions
security requirements
Confirmed period written to provider must be the validated DateTimeRange from BLoC state, not raw user input
Prevent navigation bypass: export screen must re-read confirmedReportPeriodProvider and guard against null
ui components
BlocListener wrapper
BlocConsumer as alternative pattern combining listener + builder in one widget

Execution Context

Execution Tier
Tier 7

Tier 7 - 84 tasks

Can start after Tier 6 completes

Integration Task

Handles integration between different epics or system components. Requires coordination across multiple development streams.

Implementation Notes

Use BlocConsumer instead of separate BlocListener + BlocBuilder to reduce widget tree nesting — BlocConsumer has both `listenWhen`/`listener` and `buildWhen`/`builder`. Set `listenWhen: (prev, curr) => curr is PeriodConfirmedState` to only fire on confirmation. For back-navigation interception use PopScope (Flutter 3.16+) with `onPopInvokedWithResult` to dispatch ResetPeriod before allowing the pop: `PopScope(canPop: true, onPopInvokedWithResult: (didPop, _) { if (didPop) context.read().add(ResetPeriod()); }, child: ...)`. Avoid WillPopScope which is deprecated.

For the router, use GoRouter's context.go() vs context.push() based on whether the export screen should be a new stack entry or replace the period screen — discuss with the team, but push is safer for back-navigation flow.

Testing Requirements

Widget tests: (1) emitting PeriodConfirmedState triggers provider write with correct DateTimeRange; (2) emitting PeriodConfirmedState triggers router.push to export route; (3) tapping back button dispatches ResetPeriod event; (4) non-confirmed states do not trigger navigation; (5) provider write happens before navigation call (verify call order with mock). Use MockGoRouter (or NavigatorObserver) to assert navigation calls. Use ProviderScope with read access to confirmedReportPeriodProvider in test assertions.

Component
Period Selection Screen
ui low
Epic Risks (3)
medium impact medium prob technical

The record count query is asynchronous and may take up to 1 second on a slow connection. If the BLoC does not manage loading states carefully, the UI may show a stale count or the confirm button may be briefly enabled during the loading transition, allowing premature submission.

Mitigation & Contingency

Mitigation: Define explicit BLoC states: PeriodSelectionLoading, PeriodSelectionCountLoaded, PeriodSelectionValidationError, and PeriodSelectionReady. The confirm button must only be enabled in PeriodSelectionReady. Add a debounce of 300ms on period-change events before triggering the count query to prevent excessive calls.

Contingency: If debouncing is insufficient to prevent UX degradation on slow connections, add an optimistic loading skeleton to the RecordCountBanner that clearly communicates a pending state, and keep the confirm button disabled until the count resolves.

high impact low prob integration

The confirmedReportPeriodProvider Riverpod contract between this feature and the Bufdir Export feature may not be defined yet, causing integration failures when the downstream export screen attempts to read the provider.

Mitigation & Contingency

Mitigation: Define the confirmedReportPeriodProvider as a StateProvider<DateTimeRange?> in a shared providers file at the start of this epic, before any screen code is written. Communicate the provider contract to the Bufdir Export feature team so both sides align on the same provider reference.

Contingency: If the export feature consumes the period through a different mechanism (e.g., navigation arguments), add a thin adapter that writes the confirmed period to both the Riverpod provider and the navigation argument, ensuring backward compatibility.

medium impact medium prob technical

Flutter's live region support for screen readers is inconsistent across platforms (iOS VoiceOver vs Android TalkBack), and the record count banner must be announced on every change — a pattern that has historically required platform-channel workarounds.

Mitigation & Contingency

Mitigation: Test the live region announcement on both iOS (VoiceOver) and Android (TalkBack) early in implementation using the accessibility_test harness. Reference the project's existing live-region-announcer component (664-accessibility-live-region-announcer) for a proven implementation pattern.

Contingency: If native live region support is insufficient, wrap the record count text in a Semantics widget with a programmatically updated label string, and trigger a SemanticsService.announce call on each count update as a platform-agnostic fallback.