medium priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

Rows with absolute delta > threshold are visually distinguished from non-highlighted rows
Default threshold is 25% and is applied when no override is provided
Threshold is configurable — can be passed as a widget parameter or overridden via a Riverpod provider
Highlighted rows use a background color defined in the design token system (not hardcoded)
A warning icon (e.g., Icons.warning_amber_rounded) is displayed in highlighted rows
The warning icon has a Semantics label (handled in task-010, but icon must be present here)
Text on highlighted rows meets WCAG 2.2 AA contrast ratio (minimum 4.5:1 for normal text)
Rows with null delta (prior value N/A) are never highlighted, regardless of threshold
Threshold comparison uses absolute value — both +30% and -30% trigger highlighting at default 25% threshold
No visual highlighting is applied when the diff panel is in loading or error state

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
data models
BufdirFieldDiffRow
BufdirDiffThresholdConfig
performance requirements
Threshold check is O(1) per row — no sorting or secondary passes required
security requirements
Threshold configuration must not be user-editable from the UI in the initial implementation — admin/config only
ui components
BufdirDiffRowWidget (extended with highlight state)
Design token: colorWarningSubtle (background), colorWarningIcon (icon), colorOnWarning (text)
Icons.warning_amber_rounded or equivalent design-system icon

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Add an `exceedsThreshold(double? delta, double threshold)` pure function that returns false for null delta and computes `delta.abs() > threshold`. Pass `isHighlighted` as a bool into BufdirDiffRowWidget — do not compute threshold logic inside the widget itself. Source background color from a design token constant (e.g., `AppColors.warningSubtle`) — do not use `Colors.yellow` or any hardcoded color.

Verify contrast ratio of text-on-warningSubtle during code review using the WCAG formula or a contrast checker tool. If using a Riverpod provider for threshold, create a `bufdirDiffThresholdProvider` with a default value of `25.0` that can be overridden in tests and future admin screens.

Testing Requirements

Write flutter_test widget tests covering: (1) row with delta 30% at default threshold (25%) renders highlighted background and warning icon, (2) row with delta 20% at default threshold does not render highlighted background or warning icon, (3) threshold override of 10% causes row with 15% delta to be highlighted, (4) row with null delta is never highlighted, (5) negative delta of -30% is highlighted (absolute value check), (6) highlighted row text color passes contrast ratio check using color utility or manual assertion against design token values. Write a unit test for the `exceedsThreshold(double? delta, double threshold)` utility function.

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.