medium priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Every Bufdir field in the current report has a corresponding comparison row in the diff view
Each row displays: field label, prior-period value, current-period value, and percentage delta
Positive delta is prefixed with '+' (e.g. '+5%'), negative with '-' (e.g. '-12%'), zero shown as '0%'
When prior-period value is null or absent, the prior column displays 'N/A — no prior data' (not empty, not zero)
Delta column shows 'N/A' when prior-period value is null (division by zero is not attempted)
Delta is calculated as: ((current - prior) / prior) * 100, rounded to the nearest integer
BufdirFieldRowWidget is reused for the current-value column without modification
Row layout is responsive and does not overflow on screens narrower than 360dp
Design token colors and typography are used for all text styling — no hardcoded hex values

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
data models
BufdirFieldDefinition
BufdirReportData
BufdirFieldDiffRow
performance requirements
Row rendering must be driven by a ListView.builder — no Column with unbounded children
Delta calculation must be performed once per build cycle, not on every paint
security requirements
Field values must be rendered as display strings only — no raw data exposed in widget keys or debug labels in production
ui components
BufdirFieldRowWidget (reused, current value column)
BufdirDiffRowWidget (new, wraps prior + delta columns)
BufdirDeltaBadge (new, renders formatted percentage string)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Extract the delta calculation into a pure function `double? calculatePercentageDelta(num? prior, num current)` returning null when prior is null or zero. Format the result with a `formatDelta(double?

delta)` helper that returns '+X%', '-X%', '0%', or 'N/A'. Create `BufdirDiffRowWidget` as a new widget that accepts `BufdirFieldDiffRow` (a data class holding fieldLabel, priorValue, currentValue, delta). Do not modify BufdirFieldRowWidget — compose it inside BufdirDiffRowWidget for the current-value column. Use a Row with Expanded children for the three-column layout to avoid overflow.

Ensure the 'N/A — no prior data' string is defined as a constant (or localised string key) so it can be targeted in tests.

Testing Requirements

Write flutter_test widget tests and pure Dart unit tests covering: (1) delta calculation utility — positive, negative, zero, and null-prior cases, (2) BufdirDeltaBadge renders '+5%' for positive, '-12%' for negative, '0%' for zero, 'N/A' for null prior, (3) BufdirDiffRowWidget renders all three columns with correct values from fixture data, (4) null prior-period value renders 'N/A — no prior data' in prior column and 'N/A' in delta column, (5) full BufdirPeriodDiffView widget test verifies correct number of rows rendered from a fixture with 5 fields. Use golden tests for the diff row layout if a design reference is available.

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.