high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Screen renders a scrollable list of ReportHistoryListItem widgets populated from the state layer
Filter controls appear above the list and are always visible (sticky header or fixed position above the scrollable area)
Pagination: when the user scrolls to within 3 list items of the bottom, the next page is automatically fetched
Loading state: first-load shows a full-screen shimmer or spinner with accessible label 'Loading report history'
Paginating state: a small loading indicator appears at the bottom of the list while the next page loads without hiding existing items
Empty state: when no records match the active filters, a non-error empty state illustration/message is shown with a 'Clear filters' action
Error state: a full-screen error message is shown on initial load failure with a 'Retry' button that re-triggers the initial fetch
Error state for pagination failure: a dismissable inline error snackbar is shown; existing list items remain visible
Screen route is registered in the app router and is accessible from the navigation entry point defined in the epic
Screen scaffold uses design token background colour and standard AppBar/page header component
All state transitions are driven exclusively by the state layer — no local setState calls for data

Technical Requirements

frameworks
Flutter
BLoC or Riverpod
flutter_test
apis
ReportHistoryService (via state layer)
data models
ReportHistoryRecord
ReportHistoryFilter
PaginatedResult<ReportHistoryRecord>
performance requirements
ListView.builder used for list rendering — no ListView with all children materialised at once
Page size of 20 records per fetch to balance responsiveness and network calls
Scroll position preserved when returning to the screen from a detail view within the same session
security requirements
Screen must not render if the user's role does not have access to report history — redirect to no-access screen via router guard
ui components
ReportHistoryListItem
Filter controls row (from task-003)
Shimmer loading placeholder
EmptyState widget
ErrorState widget with retry button
Pagination loading footer

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use a `NotificationListener` or a `ScrollController` with a listener attached in `initState` to detect when the user is near the bottom; dispatch a `LoadNextPage` event when `scrollPosition >= maxExtent - threshold`. Separate the screen into three named sub-areas: (1) the filter header, (2) the list body governed by a `BlocBuilder`/`Consumer`, and (3) the pagination footer. This prevents the entire screen from rebuilding on every state change. Use `AutomaticKeepAliveClientMixin` or a Riverpod `keepAlive` annotation to preserve scroll position across tab navigation.

The route guard checking role access should be in the GoRouter or Navigator 2.0 redirect logic, not inside the screen widget itself — keep the screen focused on presentation.

Testing Requirements

Widget tests: pump screen with a mock state providing a list of 3 records and assert 3 ReportHistoryListItem widgets are present. Pump with loading state and assert shimmer/spinner is shown and list is absent. Pump with empty state and assert empty state widget and 'Clear filters' button are present. Pump with error state and assert error message and retry button are present; tap retry and assert state layer fetch is called.

Integration test: use a fake service returning 2 pages of 5 records each; scroll to bottom and assert second page is loaded and list shows 10 items total.

Component
Report History Screen
ui low
Epic Risks (2)
high impact medium prob integration

The ReportSummaryMetricsWidget must display metrics as they were at time of submission, not recalculated from current data. If the metrics are not stored as a JSON snapshot in the history record at export time, the widget will either show wrong data or require a full re-aggregation on every list load.

Mitigation & Contingency

Mitigation: Ensure the Bufdir export pipeline (bufdir-report-export feature) writes a summary_metrics JSONB column in the history record at export time, containing total_activities, total_hours, and participants_reached. The UI widget reads only from this snapshot field — never from live aggregation queries.

Contingency: If snapshot data is missing for historical records (e.g., older exports before the column existed), display a 'Metrics not available for this report' placeholder in the widget rather than showing zeros or triggering a live aggregation that could return different figures.

medium impact medium prob scope

Re-export can take several seconds (it runs the full generation pipeline). Without adequate progress feedback, coordinators may tap the button multiple times, triggering duplicate exports and duplicate history records.

Mitigation & Contingency

Mitigation: Disable the re-export button immediately on first tap and show an inline progress indicator on the list item. Guard against duplicate invocations at the service layer using an in-progress flag keyed by report ID. Display a loading state on the list item throughout the operation.

Contingency: If duplicate re-export records are created (e.g., due to a race condition), the history table will show multiple entries for the same original report — which is harmless but confusing. Add a deduplication UI hint ('Re-exported N times') and a backend guard that prevents more than one in-flight re-export for the same source record ID simultaneously.