high priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

All text elements in ReportHistoryScreen, ReportHistoryListItem, and ReportSummaryMetricsWidget achieve minimum 4.5:1 contrast ratio against their background using design token colors
Large text (18sp+ or 14sp+ bold) achieves minimum 3:1 contrast ratio
Every interactive control (buttons, filter chips, list items) has a minimum touch target of 44x44dp, enforced via SizedBox or padding — not by making the visual element larger
Every interactive widget has a Semantics label that clearly describes its purpose (e.g., 'Filter by status: All reports, currently selected')
Loading state transitions announce via SemanticsService.announce with a meaningful message (e.g., 'Loading report history')
Error state transitions announce via SemanticsService.announce (e.g., 'Failed to load reports. Please retry.')
Focus traversal order matches the visual reading order (top-to-bottom, left-to-right) when navigated by keyboard or switch access
No focus traps exist — user can always navigate away from any interactive element
ReportSummaryMetricsWidget metrics are exposed to screen readers as meaningful text, not just visual numbers
Filter controls communicate their current state (selected/unselected) via Semantics.checked or Semantics.selected
The screen passes Flutter's built-in accessibility checks (SemanticsDebugger reveals no unlabeled interactive nodes)
VoiceOver on iOS and TalkBack on Android correctly read all labels, values, and state changes in the components

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
data models
bufdir_export_audit_log
annual_summary
performance requirements
Accessibility announcements must fire within one frame of the state transition to avoid stale reads by screen readers
security requirements
Screen reader announcements must not read out sensitive PII — announce only status labels, not personal identifiers or report content
ui components
Semantics widget (Flutter framework)
ExcludeSemantics for decorative elements
MergeSemantics for grouped controls
SemanticsService.announce for live regions
GestureDetector with onTap sized to 44x44dp minimum
ReportHistoryScreen
ReportHistoryListItem
ReportSummaryMetricsWidget

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use ExcludeSemantics on purely decorative icons and dividers to reduce screen reader noise. Use MergeSemantics to group list item content so VoiceOver reads it as a single announcement rather than individual fragments. For live region announcements, call SemanticsService.announce() inside the BLoC/notifier state listener — not inside build() — to avoid firing on every render. Enforce 44x44dp touch targets by wrapping smaller widgets in a SizedBox with explicit constraints and setting the Semantics.onTap callback.

Document which design token pairs were checked for contrast and their measured ratios — this serves as a record for audits. Blindeforbundet users include visually impaired users with screen readers as a primary interface, so this task has real end-user impact beyond compliance.

Testing Requirements

Widget tests (flutter_test): use tester.getSemantics() to assert every interactive widget has a non-empty label; assert touch target sizes are >= 44x44dp using tester.getSize(); assert selected/checked semantic properties on filter controls match state. Use Flutter's SemanticsDebugger in a test harness to capture the full semantics tree and assert no unlabeled interactive nodes exist. Manual testing: run VoiceOver (iOS) and TalkBack (Android) through all three components and verify announcements are correct and timely. Contrast testing: use a contrast analyzer tool against the design token color palette to document pass/fail for each text color/background combination.

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.