medium priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Initial preview screen renders without triggering any prior-period data fetch
First toggle-open of the diff panel triggers exactly one prior-period fetch via BufdirPreviewRepository
Subsequent toggle-open/close cycles do not re-fetch; cached data is used
A loading indicator (e.g., CircularProgressIndicator) is shown inside the diff panel while the fetch is in progress
Fetch errors are caught and displayed as an inline error message within the diff panel, not as a full-screen error
The AsyncNotifier state transitions correctly: initial → loading → data | error
Cached data persists for the entire session but is not persisted across app restarts
Toggle action is debounced or guarded so rapid open/close does not trigger duplicate fetches
The prior-period fetch uses the same report period identifier as the current preview session

Technical Requirements

frameworks
Flutter
Riverpod (AsyncNotifier)
flutter_test
apis
BufdirPreviewRepository.fetchPriorPeriodReport(periodId)
data models
BufdirReportPeriod
BufdirReportData
BufdirPriorPeriodState
performance requirements
Prior-period fetch must not delay initial preview render by any measurable amount
Cached data access must be synchronous (O(1) map lookup)
Network fetch should complete within 3 seconds on a standard 4G connection
security requirements
Prior-period data must be fetched using the authenticated Supabase session token
Cached data must be stored in memory only — never written to local storage or disk
Report data must not be logged to console in production builds
ui components
BufdirPeriodDiffView (toggle trigger integration)
CircularProgressIndicator (within diff panel)
Inline error widget for fetch failure

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Create a `BufdirPriorPeriodNotifier extends AsyncNotifier` with a nullable state — null means not yet loaded, AsyncLoading means in-progress, AsyncData(value) means cached. Guard the fetch with `if (state.value != null) return;` to prevent duplicate fetches. Wire the toggle action in BufdirPeriodDiffView to call `ref.read(bufdirPriorPeriodProvider.notifier).loadIfNeeded(periodId)`. Do not use `ref.watch` on the notifier in the toggle handler — use `ref.read` to avoid unnecessary rebuilds on state changes.

The diff panel widget itself should watch the provider to render the appropriate state (loading / data / error).

Testing Requirements

Write flutter_test widget and unit tests covering: (1) AsyncNotifier initial state is AsyncData.loading false / no data, (2) calling load() transitions to AsyncLoading then AsyncData, (3) second call to load() returns cached data without invoking repository, (4) repository error transitions to AsyncError with correct message, (5) widget test verifies loading indicator appears on first toggle, (6) widget test verifies no second fetch on second toggle open. Mock BufdirPreviewRepository using Riverpod overrides. Aim for 100% branch coverage on the notifier.

Component
Period Comparison Diff View
ui medium
Epic Risks (3)
medium impact medium prob integration

The preview screen must pass period, scope, and aggregation session state to the Bufdir Report Export screen via navigation arguments. If the export screen's navigation argument schema is not yet finalized when this epic begins, the handoff will require rework — potentially after TestFlight is already in use by coordinators.

Mitigation & Contingency

Mitigation: Define the shared BufdirExportNavigationArgs Dart class jointly with the Bufdir Report Export feature team before this epic starts. Store it in a shared models package both features depend on. Treat the class as an API contract with a minor-version bump policy.

Contingency: If the export screen's argument schema changes after the preview screen is implemented, the BufdirPreviewService session state model can be adapted with a compatibility shim. The preview screen itself requires only a one-line navigation call change.

medium impact low prob technical

The period diff view loads prior-period data on demand and renders it inline with the current report. On a large report (many sections, many fields) combined with slow Supabase connectivity, the diff overlay could block the UI or produce a janky re-render that degrades the coordinator experience during the pre-submission review.

Mitigation & Contingency

Mitigation: Load prior-period data in a background Riverpod FutureProvider that starts prefetching when the preview screen mounts (not when the user taps the diff toggle). Show a shimmer placeholder on each field row's prior-period column while loading. Cache prior-period data in BufdirPreviewRepository using the same local cache as current-period data.

Contingency: If diff view performance is unacceptable on TestFlight devices, disable the toggle for the initial TestFlight release and ship the diff view in the following sprint after profiling the Supabase query plan and adding an appropriate Postgres index on the prior-period data table.

high impact medium prob scope

The full preview screen will be reviewed by coordinators on TestFlight who will cross-reference it against their physical Bufdir reporting forms. Any section ordering difference or label mismatch discovered during UAT will require a fix before the feature can be signed off, potentially delaying the entire Bufdir reporting pipeline.

Mitigation & Contingency

Mitigation: Conduct a pre-TestFlight alignment review with at least one coordinator from NHF and one from HLF using a static screenshot of the preview screen layout. Obtain written sign-off on section order and field labels before distributing to the full test group of 5-8 people.

Contingency: If label mismatches are found during TestFlight UAT, update BufdirReportStructureMapper constants and rebuild. Since the mapper is a pure Dart class with no persisted state, corrections are deployed in the next TestFlight build with no database migration required.