high priority medium complexity testing pending frontend specialist Tier 5

Acceptance Criteria

Every text element in all four components passes 4.5:1 contrast ratio for normal text (≤18pt / ≤14pt bold) using design token colors from styles.css — verified with Flutter's built-in contrast checker or an external tool like Colour Contrast Analyser
Large text elements (≥18pt or ≥14pt bold) pass 3:1 contrast ratio minimum
All tappable elements (buttons, list rows, date chips, dialog actions) have a minimum touch target of 44×44 dp — use SizedBox or Padding to pad smaller visual targets without changing layout
Semantics traversal order on each screen matches top-to-bottom, left-to-right visual reading order when tested with VoiceOver (iOS) and TalkBack (Android)
Every form field and picker in ExportDateRangePicker has a Semantics label or hint describing its purpose (e.g., 'Start date, required', 'End date, required')
State changes (loading spinner appearing, success/error SnackBar, DuplicateWarning banner) announce via SemanticsService.announce or equivalent live region so screen reader users hear the update without moving focus
ExportConfirmationDialog traps focus within its bounds when open — tab/focus cycling does not escape to the background screen
On ExportConfirmationDialog dismiss, focus is restored to the element that triggered the dialog (the Confirm/Export button)
All icon-only buttons (e.g., close/X, calendar icon) have a Semantics label that conveys action, not icon name
A written accessibility audit report is produced listing each criterion checked, pass/fail result, and description of any fix applied

Technical Requirements

frameworks
Flutter
BLoC
apis
Flutter Semantics API
SemanticsService.announce
FocusScope / FocusNode for dialog focus trapping
ExcludeSemantics / MergeSemantics as needed
performance requirements
Accessibility fixes must not introduce layout reflows or jank — use LayoutBuilder or SizedBox for target padding, not MediaQuery in hot paths
security requirements
Sensitive export data shown in confirmation dialog must not be announced by VoiceOver when the dialog is not focused — use ExcludeSemantics on background content while dialog is open
ui components
Semantics widget for custom interactive elements
MergeSemantics for compound list row widgets
ExcludeSemantics for decorative icons and redundant labels
FocusScope with canRequestFocus and autofocus for modal focus trapping

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Start with the automated tap-target guidelines — they are the fastest to find and fix. Then manually walk each screen with VoiceOver enabled on a real iOS device (simulator VoiceOver is unreliable for focus-order testing). For focus trapping in ExportConfirmationDialog, wrap the dialog body in a FocusScope with a defined FocusNode and call FocusScope.of(context).requestFocus() on dialog open. Store the previously focused FocusNode before opening and restore it in the dialog's onDismiss callback.

For live region announcements, prefer SemanticsService.announce over rebuilding the widget tree — it is lower overhead and does not cause layout thrash. When adding Semantics labels to date fields, include the current value in the label string (e.g., 'Start date: 1 March 2026') so screen reader users hear the selected value without needing to activate the field. Reference WCAG 2.2 success criteria 1.4.3, 1.4.11, 2.4.3, 2.5.3, and 4.1.3 in the audit report.

Testing Requirements

Manual audit using VoiceOver on iOS 17+ and TalkBack on Android 12+ physical devices. Automated checks: add semantics golden tests using flutter_test's SemanticsController to assert label presence on key interactive widgets. Write at least one widget test per component that calls tester.getSemantics() and verifies label, hint, and enabled state. Run Flutter's built-in accessibility checks via AccessibilityGuideline (androidTapTargetGuideline and iOSTapTargetGuideline) in existing widget tests.

Document all manual test steps in the audit report so they can be re-run after future changes.

Component
Accounting Export Screen
ui medium
Dependencies (3)
Implement a RoleGuard wrapper inside AccountingExportScreen that reads the current user role from the role state provider and conditionally renders the export date picker and trigger button only when the role is coordinator or org_admin. Peer mentors see a read-only history panel without any export action. The guard must re-evaluate on role switch (supporting multi-role users). Add a Semantics widget with an excludeSemantics flag on hidden elements so screen readers are not confused by off-screen controls. epic-accounting-system-export-ui-task-007 Add file download handling to AccountingExportScreen: on ExportSuccess state the BLoC emits a signed file URL; the screen passes this to a FileDownloadHandler that uses url_launcher to open the file or triggers a platform share sheet. Show a SnackBar with a persistent 'Download' action button. On ExportHistoryPanel row tap, fetch a fresh signed URL from the export run repository and invoke the same download handler. Ensure the download action has an accessible label for VoiceOver. epic-accounting-system-export-ui-task-008 Assemble the full AccountingExportScreen widget that hosts ExportDateRangePicker, a trigger button (visible only to coordinator and org admin roles), ExportConfirmationDialog (shown as modal bottom sheet), and ExportHistoryPanel in a scrollable column. Read exporter type (Xledger vs Dynamics) from BLoC ConfigLoaded state and display an org-specific label. Show a role-guard widget that hides the export action for peer mentor role. Apply design token colours and spacing. Ensure the screen passes WCAG 2.2 AA at all interactive elements. epic-accounting-system-export-ui-task-006
Epic Risks (2)
medium impact medium prob technical

Export operations may take several seconds, and the UI must handle all intermediate states (loading, partial success, failure, duplicate warning) without leaving the coordinator on a blank or unresponsive screen. Missing state handling causes confusion and potentially double-submissions.

Mitigation & Contingency

Mitigation: Design the BLoC state machine with explicit states for each transition before writing any widget code: ExportIdle, ExportDuplicateWarning, ExportInProgress, ExportSuccess, ExportPartialSuccess, ExportFailed. Each state maps to a distinct UI. Widget tests cover all states.

Contingency: If a loading state is missed in production, surface a generic error state with a retry action rather than leaving the UI stuck. Add a timeout on the Edge Function call (default 30 seconds) that transitions to ExportFailed with a user-readable message.

high impact medium prob technical

The custom Export Date Range Picker may not be fully navigable with VoiceOver if the underlying Flutter date widgets do not expose the correct semantic tree. This is a critical accessibility failure for Blindeforbundet users who rely on screen readers.

Mitigation & Contingency

Mitigation: Use Flutter's built-in DateRangePicker as the base and wrap with explicit Semantics nodes for start and end labels. Test with VoiceOver on a physical iOS device as part of the definition of done for this component. Reference the existing AccessibilityTestHarness pattern used elsewhere in the app.

Contingency: If the custom picker fails accessibility audit, replace it with two independent DatePicker fields (start and end) using Flutter's standard accessible date input, which has broader VoiceOver support than range variants.