high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Widget renders all three metrics (total activities, total hours, participants reached) from the provided snapshot data model
Layout is compact enough to fit inside a list item row without overflow on screens 320 dp wide and above
All typography and spacing values are sourced exclusively from the design token system — no hardcoded sizes or colours
Each metric value is wrapped in a Semantics widget with a descriptive label readable by screen readers (e.g., 'Total activities: 12')
Text contrast ratio between metric value and background meets WCAG 2.2 AA (≥ 4.5:1 for normal text)
Widget accepts null/zero values gracefully and displays '—' or '0' without throwing
Widget is stateless and accepts all data via constructor parameters
Golden test or widget test confirms visual layout does not regress
Widget is exported from the component barrel file and can be imported without referencing internal file paths

Technical Requirements

frameworks
Flutter
flutter_test
data models
ReportSummarySnapshot (totalActivities, totalHours, participantsReached)
performance requirements
Widget build completes in a single frame — no async operations inside build()
No unnecessary rebuilds: widget is const-constructible when data is available at compile time
security requirements
No PII is displayed — snapshot metrics are aggregate figures only
ui components
Row or Wrap layout container
Individual MetricChip sub-widget (label + value pair)
Semantics wrapper per metric
Design token typography style application

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Model this as a purely stateless StatelessWidget accepting a single `ReportSummarySnapshot` data class (or equivalent). Use a Row of equally-spaced MetricChip widgets where each chip shows a label token string and a value. Source all text styles from `AppTextStyles` tokens and all spacing from `AppSpacing` tokens — never use raw pixel values. The WCAG contrast requirement means metric values should use the primary text token colour against the list item background token; verify in both light and dark theme if the app supports them.

Keep the widget narrow-first: on very small screens (320 dp), allow the Row to wrap using a Wrap widget rather than overflowing. Expose a named constructor `ReportSummaryMetricsWidget.loading()` that shows shimmer placeholders so the parent list item can render before data loads.

Testing Requirements

Widget tests using flutter_test: render widget with typical values and assert all three metric texts are present in the widget tree; render with zero values and assert fallback display; render with null model and assert no exception is thrown. Use `tester.pump()` to settle the frame. Add a golden test for the compact layout at 375 dp width. Verify Semantics tree contains the correct labels 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.