Implement remove and replace actions on thumbnail
epic-receipt-capture-and-attachment-ui-accessibility-task-003 — Add Remove and Replace icon-buttons to the thumbnail widget. Remove clears the attached receipt and reverts the parent form to an unattached state. Replace re-opens the Receipt Camera Sheet. Both actions must be confirmed with a brief haptic feedback pulse and must be reachable via keyboard/switch-access focus order.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Add onRemove and onReplace as optional named parameters to ReceiptThumbnailPreview. Position buttons as an overlay row at the bottom-right or top-right of the thumbnail container using Positioned within the existing Stack from task-002. Use IconButton with BoxConstraints(minWidth: 44, minHeight: 44) to guarantee tap targets. Wrap each button in a Tooltip with a descriptive message ('Remove receipt', 'Replace receipt') — this also satisfies the accessibility label requirement that task-004 will formalize with Semantics.
Do NOT implement any confirmation dialog in this task — haptic feedback is the only confirmation signal required per spec. The Receipt Camera Sheet opening is the caller's responsibility via the onReplace callback; this widget has no knowledge of navigation.
Testing Requirements
Write flutter_test widget tests: (1) both buttons visible when callbacks provided, (2) neither button visible when both callbacks null, (3) onRemove fires exactly once on Remove tap, (4) onReplace fires exactly once on Replace tap, (5) haptic feedback is triggered (mock HapticFeedback channel in test). For focus order, use FocusTraversalPolicy tests or manual verification notes in the PR since automated focus order testing in flutter_test is limited. Document manual switch-access test steps in PR description.
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.
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.