high priority low complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

Gap banner is only shown when confirmedReportPeriodProvider returns a non-null previous DateTimeRange
Gap is computed as selectedPeriod.start.difference(previousPeriod.end).inDays and displayed as a positive integer
When gap > 0: message reads 'This period starts X days after your last submission' using design token typography
When gap == 0: message reads 'This period continues directly from your last submission' with a success-style indicator
When gap < 0 (overlap): message reads 'Warning: This period overlaps with your last submission by X days' using warning color token
Gap banner renders beneath the record count row inside RecordCountBanner with an 8dp vertical separator
Gap banner is accessible: container has Semantics label='Gap information: [message text]' and live region set to assertive for changes
Banner is hidden (SizedBox.shrink) when no previous period exists, with no layout space consumed
Gap calculation handles null previousPeriod.end gracefully without throwing
Gap text uses correct singular/plural: '1 day' vs 'X days'

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
data models
DateTimeRange
confirmedReportPeriodProvider
performance requirements
Gap calculation is synchronous — no async work or loading states needed
Widget rebuild triggered only when confirmedReportPeriodProvider changes, not on every BLoC state update
security requirements
No sensitive data displayed in the banner — dates only, no personal information
ui components
RecordCountBanner (parent widget being extended)
Consumer widget wrapping the gap section to read confirmedReportPeriodProvider
Semantics widget for accessibility live region
Design token color and typography references for gap severity states

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Integration Task

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

Implementation Notes

Use a Consumer widget inside RecordCountBanner's build method to watch confirmedReportPeriodProvider independently from the BLoC state, so gap recalculates whenever the provider changes. Compute gap as: `final gap = selectedStart.difference(confirmedEnd).inDays;` where confirmedEnd is previousPeriod.end. Use a switch or if-else on gap sign to select message and color token. Keep the gap section in a separate private _GapBanner widget or method to keep RecordCountBanner readable.

Do not import .js files. Design token references should use AppColors/AppTextStyles from the token system, not hardcoded hex values. Wrap the entire gap section in a Semantics widget with `liveRegion: true` and `label:` set to the message string for VoiceOver/TalkBack compliance (WCAG 2.2 AA).

Testing Requirements

Widget tests using flutter_test: (1) verify banner is absent when confirmedReportPeriodProvider is null; (2) verify correct positive-gap message with exact day count; (3) verify zero-gap continuation message; (4) verify overlap warning message and color; (5) verify Semantics label matches displayed text; (6) verify singular/plural day wording. All tests use ProviderScope overrides to inject mock confirmedReportPeriodProvider values. No golden tests required.

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.