high priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

RecordCountBanner is a StatelessWidget that receives no constructor arguments — it reads state from the nearest BlocProvider<PeriodSelectionBloc>
When state is PeriodSelectionLoading, widget renders a centered CircularProgressIndicator using design token color
When isCountLoading is true on PeriodSelectionLoaded, widget renders a loading spinner in place of the count number
When recordCount is non-null and isCountLoading is false, the count is displayed as a prominent number (design token textHeadingLarge or equivalent) with a label 'activities in selected period'
When showIncompleteWarning is true, a warning banner is rendered below the count with text 'Selected period includes dates beyond last complete reporting period' and an amber/warning color from design tokens
Widget uses BlocBuilder with buildWhen to only rebuild when isCountLoading, recordCount, or showIncompleteWarning change — not on every state transition
All text uses design token typography values (no hardcoded font sizes or weights)
All colors use design token color values (no hardcoded hex colors)
Widget renders correctly at screen widths 320px, 375px, and 428px (golden tests or screenshot tests)
Widget does not overflow at any font scale factor from 0.85 to 1.3

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
data models
PeriodSelectionLoaded
PeriodSelectionState
performance requirements
buildWhen must filter state changes to prevent unnecessary rebuilds
Widget must not trigger parent widget rebuilds via state or notifiers
security requirements
Record count value must be displayed as-is — no interpolation with external data that could enable injection
ui components
CircularProgressIndicator
Text (design token typography)
Container/Card (design token spacing and color)
Icon (warning icon from Flutter material icons)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Integration Task

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

Implementation Notes

Use BlocBuilder with a buildWhen that checks: `previous is! PeriodSelectionLoaded || next is! PeriodSelectionLoaded || (previous as PeriodSelectionLoaded).isCountLoading != (next as PeriodSelectionLoaded).isCountLoading || (previous as PeriodSelectionLoaded).recordCount != (next as PeriodSelectionLoaded).recordCount || (previous as PeriodSelectionLoaded).showIncompleteWarning != (next as PeriodSelectionLoaded).showIncompleteWarning`. Structure the widget's build return as a Column with: (top) a count display area that switches between spinner and count text, (bottom) an AnimatedSwitcher-wrapped warning banner that animates in/out smoothly.

Reference design tokens via AppDesignTokens.typography.headingLarge and AppDesignTokens.colors.warning — never hardcode. The warning banner should have role-appropriate contrast: ensure amber warning color meets WCAG 2.2 AA 4.5:1 ratio against its background.

Testing Requirements

Widget tests using flutter_test. Required test cases: (1) loading spinner shown when isCountLoading is true — pump widget with bloc in loading state, verify CircularProgressIndicator present; (2) count displayed correctly — pump with recordCount: 42, verify text '42' present; (3) warning banner shown — pump with showIncompleteWarning: true, verify warning text present; (4) warning banner hidden — pump with showIncompleteWarning: false, verify warning text absent; (5) initial state — pump with PeriodSelectionInitial, verify widget renders a sensible empty/loading state without overflow; (6) overflow test — pump with textScaleFactor 1.3, verify no overflow exceptions. Use MockPeriodSelectionBloc with bloc_test's MockBloc or a fake bloc that emits controlled states.

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.