high priority low complexity integration pending frontend specialist Tier 4

Acceptance Criteria

Tapping the re-export button on ReportHistoryListItem triggers ReportReexportCoordinator exactly once per tap
While re-export is in progress, the list item displays a CircularProgressIndicator that replaces the re-export button and is non-interactive
On successful re-export, a success SnackBar or inline status indicator is shown with the updated export timestamp
On re-export failure, an error message is surfaced inline on the list item and the re-export button is restored to interactive state
Tapping re-download invokes the Supabase Storage file client to retrieve the existing report file by its stored reference
After successful file retrieval, the system share/download sheet (share_plus) is triggered with the file as the share payload
Re-download button shows a loading state while the file is being fetched from Supabase Storage
Re-download failure (file not found, network error) surfaces a user-readable error message without crashing
All buttons have Semantics labels that describe the action and current state (e.g., 'Re-export report for Q1 2024, in progress')
VoiceOver/TalkBack announces state transitions (loading started, success, error) via live region or SemanticsService.announce
Re-export and re-download buttons are disabled (not hidden) while their respective operations are in progress
The coordinator receives the correct report period ID from the list item's data model
No duplicate requests are sent if the user taps the button multiple times before the first response

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
Supabase Storage SDK
share_plus Flutter Package
data models
bufdir_export_audit_log
annual_summary
performance requirements
Re-export trigger must respond to tap within 200ms (show loading state)
File retrieval from Supabase Storage must complete within 10 seconds on a standard mobile connection
No jank on list scroll while an item is in loading state
security requirements
Supabase Storage signed URLs used for file retrieval — never expose raw storage paths
Signed URLs must be short-lived (default 1 hour expiry as per integration security policy)
Re-export action must only be available to users with coordinator or admin role (enforce via RLS and UI guard)
Audit log entry (bufdir_export_audit_log) must be created server-side for every re-export action
File retrieval must respect per-bucket RLS — cross-organisation access must be impossible
ui components
ReportHistoryListItem
CircularProgressIndicator (inline, replaces button during loading)
SnackBar or inline StatusBadge for success/error feedback
Semantics widget wrapping all interactive controls

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Use a local per-item loading state variable inside the BLoC/notifier — avoid storing loading state in the global list state to prevent unnecessary list rebuilds. Wrap the re-export and re-download handlers in a guard that checks `isLoading` before dispatching to prevent double-tap duplicate requests. Use SemanticsService.announce() for state transitions rather than relying solely on widget rebuilds, as screen readers may not detect rapid state changes. For file retrieval, call Supabase Storage's createSignedUrl with a short TTL and pass the URL to share_plus ShareFile — do not download the file to app storage unless sharing requires a local file path.

Coordinate with the audit logging backend: the Edge Function handling re-export should write to bufdir_export_audit_log; the mobile client does not write audit rows directly.

Testing Requirements

Widget tests (flutter_test): verify that tapping re-export button emits the correct event to the BLoC/notifier; verify loading state renders CircularProgressIndicator; verify success state restores button and shows feedback; verify error state shows error message and restores button. Mock ReportReexportCoordinator and Supabase Storage client. Integration tests: verify coordinator receives correct period ID; verify share_plus is invoked with a non-null file payload. Accessibility test: verify Semantics tree contains meaningful labels on all button states; verify live region announcements fire on state transitions.

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.