high priority low complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

Every diff row has a Semantics widget wrapping it with a descriptive label string
Label for a positive delta reads: '{FieldName}: increased by {X} percent'
Label for a negative delta reads: '{FieldName}: decreased by {X} percent'
Label for a zero delta reads: '{FieldName}: unchanged'
Label for a null prior value reads: '{FieldName}: no prior period data available'
Highlighted rows append ', flagged as significant change' to the label
Warning icon in highlighted rows is excluded from the accessibility tree (ExcludeSemantics) to avoid duplicate announcements
BufdirAccessibilityUtils.buildDiffRowLabel() is used for all label generation — no inline string construction in widgets
Semantic labels use plain language with no special characters, abbreviations, or symbols (e.g., '%' is spelled out as 'percent')
Screen reader announces the complete label in a single pass — no broken-up announcements from child widgets

Technical Requirements

frameworks
Flutter (Semantics, ExcludeSemantics)
flutter_test
data models
BufdirFieldDiffRow
performance requirements
Semantics label construction is a pure string operation — no async work
security requirements
Semantic labels must not include raw database IDs or internal field keys — only human-readable field names
ui components
Semantics widget (wrapping each BufdirDiffRowWidget)
ExcludeSemantics widget (wrapping warning icon)

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Add `buildDiffRowLabel({required String fieldName, required double? delta, required bool isHighlighted})` to BufdirAccessibilityUtils. Use a switch/if chain on delta: null → 'no prior period data available', 0 → 'unchanged', positive → 'increased by X percent', negative → 'decreased by X percent' (use delta.abs()). Append ', flagged as significant change' when isHighlighted is true.

Wrap the entire BufdirDiffRowWidget content in a single `Semantics(label: label, child: ExcludeSemantics(child: ...))` so the label is the sole announcement. Wrap the warning icon specifically in `ExcludeSemantics` to prevent double announcement. This task is high priority because the app serves blind and visually impaired users via Norges Blindeforbund — accessibility is non-negotiable.

Testing Requirements

Write flutter_test unit tests for BufdirAccessibilityUtils.buildDiffRowLabel() covering all branches: positive delta, negative delta, zero delta, null prior (N/A), highlighted positive, highlighted negative. Write widget tests using the tester.getSemantics() API or find-by-semantics-label to assert correct labels are present on rendered diff rows. Verify that the warning icon widget is wrapped in ExcludeSemantics by checking that no semantics node with the icon's tooltip/label appears in the semantics tree. Test accessibility on a real iOS device with VoiceOver enabled as a manual acceptance step before marking the task done.

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.