critical priority medium complexity testing pending testing specialist Tier 4

Acceptance Criteria

An audit document is produced listing every interactive element in the receipt capture flow with its actual VoiceOver label, role, and value as read on a physical iOS device
All interactive elements (expense amount field, attachment indicator, camera trigger button, thumbnail, remove button, replace button) have unique, descriptive VoiceOver labels that do not rely on color or position
The threshold crossing live region announcement fires and interrupts current VoiceOver speech (assertive) when the amount crosses into 'required'
The threshold crossing live region announcement queues correctly (polite) when reverting to 'optional'
VoiceOver focus order follows a logical reading order through the expense form — no focus traps, no skipped interactive elements
The camera sheet's interactive elements are fully accessible via VoiceOver swipe navigation
The receipt thumbnail has a label indicating the attachment is present (e.g., 'Receipt image. Double tap to view, swipe up for actions')
The remove receipt action is reachable via VoiceOver custom actions or swipe-up rotor actions and reads a confirmation label before execution
Zero blocking issues remain unfixed at sign-off — all findings classified as blocking must have corresponding code fixes committed
Non-blocking issues are documented in a follow-up issue with priority labels and not blocking sign-off

Technical Requirements

frameworks
Flutter
performance requirements
Receipt thumbnail must load within 2 seconds after capture so VoiceOver can focus the new element promptly
security requirements
Audit must confirm that VoiceOver does not read any hidden financial data or PII that is visually hidden but present in the semantics tree
ui components
Expense amount text field
ReceiptAttachmentIndicator
Camera capture bottom sheet
ReceiptThumbnailPreview
Remove/Replace action buttons

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Use a standardized audit checklist template shared across the VoiceOver (task-010) and TalkBack (task-011) audits to ensure consistent coverage. Enable 'Speak Hints' in VoiceOver settings during the audit to catch missing hint strings. Use VoiceOver's rotor to test custom actions on the thumbnail. When fixing issues found during audit, isolate each fix to the minimal Semantics change required — avoid broad refactors that could introduce regressions in other flows.

After fixes, run the automated semantic label widget tests from tasks 007 and 008 to confirm no regressions. TestFlight build recommended for audit to match production binary behavior (vs debug mode which may differ in semantics tree handling).

Testing Requirements

Manual structured audit on a physical iPhone (iOS 17+) with VoiceOver enabled. Use a structured audit checklist covering: (1) label completeness, (2) role accuracy, (3) focus order, (4) live region timing, (5) action discoverability, (6) no focus traps. Document each finding with: element name, expected behavior, actual VoiceOver output, pass/fail classification (blocking/non-blocking), and fix reference if applicable. Fixes must be validated with a re-test pass on the same device before sign-off.

Epic Risks (2)
medium impact medium prob technical

Flutter's accessibility live region support (SemanticsProperties.liveRegion) has known inconsistencies between iOS VoiceOver and Android TalkBack, and between Flutter versions. Threshold-crossing announcements may fail to fire or double-fire, breaking the accessibility contract for Blindeforbundet users.

Mitigation & Contingency

Mitigation: Test live region announcements on physical devices with VoiceOver and TalkBack enabled from the first iteration. Use the AccessibilityLiveRegionAnnouncer component pattern already established in the project. Verify announcement timing relative to Bloc state emissions to avoid double-fires.

Contingency: If Flutter live regions prove unreliable, implement a platform-channel fallback that calls UIAccessibility.post(notification:) on iOS and AccessibilityManager.sendAccessibilityEvent() on Android directly, bypassing Flutter's abstraction.

medium impact low prob integration

The org-configurable threshold must be available at form-render time. If the threshold configuration is fetched asynchronously and not cached, the indicator may briefly show the wrong state (e.g., 'optional' before the threshold loads), confusing users and potentially allowing invalid submissions.

Mitigation & Contingency

Mitigation: Ensure the receipt threshold validator loads and caches the org configuration at app startup or organization selection time, not lazily on form open. Use a loading state in the indicator widget rather than defaulting to 'optional' while configuration is pending.

Contingency: If startup caching is not feasible, treat an unknown threshold as 'receipt required' (fail safe) and surface a clear loading indicator until the configuration resolves, preventing invalid submissions while the config loads.