high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Year filter control displays available years derived from the loaded report history (not a hardcoded list) plus an 'All years' option
Period type filter offers the supported period types (e.g., half-year, quarter, full-year) plus an 'All types' option
Status filter offers available statuses (e.g., completed, pending, failed) plus an 'All statuses' option
Selecting any filter value immediately dispatches an event/notifier update to the state layer without requiring a separate 'Apply' button
Filter state is preserved when navigating away from and back to the report history screen within the same app session
Changing a filter triggers a new data fetch from the state layer; the list does not reload if the same filter value is re-selected
Each filter control has a visible label above or beside it meeting WCAG 2.2 AA contrast ratio (≥ 4.5:1)
Filter controls are keyboard/switch-access navigable with correct focus order
Selected filter values are reflected in the control's displayed state after a hot-restart-simulated re-render (state layer is the source of truth)
No hardcoded pixel sizes or colours — all design tokens used

Technical Requirements

frameworks
Flutter
BLoC or Riverpod
flutter_test
data models
ReportHistoryFilter (year?: int, periodType?: PeriodType, status?: ReportStatus)
performance requirements
Filter change dispatches a single event to the state layer; no debounce needed as each selection is discrete
Available year list derived reactively from loaded report data — no separate API call
security requirements
Filter parameters are passed to the service layer as typed enums, not raw strings, to prevent injection
ui components
Labelled DropdownButtonFormField or custom AppDropdown for year
Labelled DropdownButtonFormField or SegmentedButton for period type
Labelled DropdownButtonFormField or SegmentedButton for status
Horizontal scrollable filter row container

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define a `ReportHistoryFilter` value object (immutable, copyWith) that encapsulates the three filter dimensions. The BLoC event should be `ReportHistoryFilterChanged(ReportHistoryFilter)` or the Riverpod notifier should expose an `updateFilter(ReportHistoryFilter)` method. Use `context.read<>()` (Riverpod) or `BlocProvider.of<>()` (BLoC) to dispatch filter changes without triggering a rebuild of the entire screen — only the list portion should rebuild via a `BlocBuilder`/`Consumer`. Derive the year options from the state layer's loaded data rather than calling the service again; keep the filter row as a stateless widget that reads current filter from the state and fires callbacks.

Place filter controls in a horizontally scrollable `SingleChildScrollView` row so they do not break layout on narrow screens.

Testing Requirements

Widget tests: render filter controls and assert all three are present. Simulate selection of a specific year and assert the state layer receives the correct filter event (use a fake BLoC/provider). Simulate selecting 'All years' after a specific year and assert filter resets. Verify that selecting the currently-active filter value does not fire a duplicate event.

Integration test: place filter controls inside the real BLoC/provider and assert that selecting a filter updates the provider's state synchronously.

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.