medium priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

BufdirPeriodDiffView is a separate StatefulWidget in its own file under the bufdir feature directory
Diff toggle button is rendered in BufdirPreviewScreen AppBar (left of Export button) and has a minimum touch target of 48×48 dp
Toggle state is stored as a local bool in BufdirPreviewScreen's State — no global provider for this ephemeral UI state
When toggle is off (default), BufdirPeriodDiffView is not present in the widget tree (conditional rendering, not Visibility/Opacity hide)
When toggle is turned on, BufdirPeriodDiffView animates into view using a slide-from-right AnimatedSlide or an expandable AnimatedSize — animation duration 250–350 ms
While prior-period data is loading, the panel displays a CircularProgressIndicator centred within its bounds
Panel has a fixed or proportional height that does not push the section list off screen — maximum 40% of screen height
Toggling the panel off removes it from the tree cleanly — no lingering animations or leaked state
Diff toggle button Semantics reflects current state: label changes between 'Show period comparison' and 'Hide period comparison'
BufdirPeriodDiffView exposes a slot for actual diff content (placeholder acceptable at this task's scope — content is a future task)

Technical Requirements

frameworks
Flutter
Riverpod
data models
BufdirPriorPeriodData
performance requirements
Animation must run at 60 fps on a mid-range device — use AnimatedSlide or SlideTransition with a pre-built animation controller, not AnimatedContainer for position
Prior-period data fetch must be initiated only when the toggle is turned ON for the first time — lazy loading, not eager
Widget removal from tree must be immediate after animation completes — use AnimationController.addStatusListener to remove widget post-dismissal
security requirements
Prior-period data must be fetched with the same role-based access check as current-period data — no elevation of privilege via the diff panel
ui components
IconButton (diff toggle, in AppBar)
BufdirPeriodDiffView (new StatefulWidget)
AnimatedSlide or AnimatedSize (panel animation)
CircularProgressIndicator (loading state inside panel)
Placeholder content slot (empty Container with fixed height)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Manage toggle with a simple bool _isDiffVisible in BufdirPreviewScreen State — setState is sufficient and appropriate here since this is pure local UI state with no cross-widget impact. Use an if (_isDiffVisible) BufdirPeriodDiffView(...) conditional in the Column/Stack rather than Visibility, so Flutter actually removes the widget from the tree and triggers dispose(). In BufdirPeriodDiffView, initiate the prior-period fetch in initState using ref.read(bufdirPriorPeriodProvider(reportId).future) — do not fetch unconditionally in the parent. Use a Stack layout in BufdirPreviewScreen if the slide-from-right approach is chosen: place BufdirPeriodDiffView in a Positioned right-aligned overlay above the section list.

If the expandable-section approach is preferred instead (simpler), place it above the ListView and use AnimatedSize wrapping a Column — this avoids z-index complexity. Document the chosen approach with a comment explaining the trade-off. The panel header should include a close button (X icon) as an alternative dismiss mechanism to the toggle — this aids users who find the toggle in the AppBar after the panel is open.

Testing Requirements

Write widget tests for: (1) diff panel is absent from widget tree when toggle is off, (2) diff panel is present in widget tree after toggle is tapped, (3) panel is removed from tree after toggle is tapped again, (4) CircularProgressIndicator is shown inside panel while AsyncLoading, (5) Semantics label on toggle button updates to reflect current state. Use tester.tap on the toggle button and pumpAndSettle to verify animation completion. Use find.byType(BufdirPeriodDiffView) to assert presence/absence. Mock the prior-period data provider via ProviderScope override to simulate loading and loaded states.

Verify animation does not throw with a pump(Duration(milliseconds: 400)) after toggle.

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.