high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

Widget displays period range (e.g., 'Jan 2025 – Jun 2025') formatted from the report's start and end date fields
Generation timestamp is displayed in locale-aware format (Norwegian locale: dd.MM.yyyy HH:mm)
Generating user's display name is shown with a fallback of 'Unknown user' when null
Re-download button triggers the provided `onRedownload` callback; re-export button triggers `onReexport` callback
ReportSummaryMetricsWidget is embedded and receives the snapshot data from the report model
Both action buttons have a minimum touch target of 48 Γ— 48 dp per WCAG 2.2 AA
Each action button has a Semantics label distinguishing its purpose (e.g., 'Re-download report Jan–Jun 2025')
All colours, typography, radii, and spacing use design tokens exclusively
Widget handles loading state for re-export button (shows spinner, disables tap) while re-export is in progress
Widget is tested with widget tests confirming all text fields and button callbacks

Technical Requirements

frameworks
Flutter
flutter_test
data models
ReportHistoryRecord (periodStart, periodEnd, generatedAt, generatedByName, summarySnapshot)
ReportSummarySnapshot
performance requirements
Widget is stateless except for the re-export loading indicator, which must be managed via a local ValueNotifier or passed in as state from the parent
No blocking I/O inside build(); callbacks are fire-and-forget at the widget layer
security requirements
Generating user name is display-only; no email or user ID is surfaced in the UI
Action callbacks must be null-safe: if a callback is null, the corresponding button is rendered as disabled
ui components
ReportSummaryMetricsWidget
AppButton (re-download, re-export variants)
Period range text
Generation metadata row (timestamp + user)
Loading indicator overlay for re-export button

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Accept the full ReportHistoryRecord via constructor along with named `onRedownload` and `onReexport` VoidCallback parameters. Wrap action buttons in a Row aligned to the trailing edge. Use `intl` package for date formatting with the 'nb_NO' locale β€” import the locale data in main.dart if not already done. For the re-export loading state, accept an `isReexporting` bool parameter rather than managing state internally, so the parent BLoC/Riverpod layer controls the loading indicator.

Apply `Semantics(label: '...', button: true)` to each action button with the period range baked into the label for screen reader context. Ensure the entire list item is not itself tappable β€” only the discrete action buttons are interactive.

Testing Requirements

Widget tests: render with a complete ReportHistoryRecord and assert period range text, timestamp, user name, and both action buttons are visible. Tap re-download button and assert `onRedownload` callback is called once. Tap re-export button and assert `onReexport` callback is called once. Render with null generatedByName and assert fallback text appears.

Render with re-export loading state active and assert button is disabled and spinner is visible. Verify Semantics labels on both buttons using `tester.getSemantics()`.

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.