critical priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Camera roll source option has Semantics label: 'Select from camera roll' with button: true
File system source option has Semantics label: 'Select from files' with button: true
Each preview row (pending file) has a Semantics label: '${fileName}, ${humanReadableSize}, ${mimeType}' for screen reader traversal
Remove button on each preview row has Semantics label: 'Remove ${fileName} from selection'
During upload, SemanticsService.announce fires at 25%, 50%, 75% progress milestones and at completion: 'Upload ${percent}% complete' / 'Upload complete'
MIME rejection produces a live region announcement: 'File type not supported. Only images and PDFs are allowed.'
Limit-exceeded inline error message ('Maximum 5 attachments reached') is also announced via SemanticsService when first displayed
All buttons and tappable rows meet the 44x44 pt minimum touch target
Confirm and Cancel buttons have descriptive semantic labels: 'Confirm attachment selection' and 'Cancel attachment selection'
Widget test confirms semantic labels are present on both source options and on at least one preview row

Technical Requirements

frameworks
Flutter
BLoC
apis
SemanticsService.announce
Flutter Semantics widget
attachment-upload-service upload stream
data models
PendingAttachment (fileName, mimeType, fileSizeBytes)
UploadProgress (fileName, percent, status)
performance requirements
Upload progress announcements must be throttled — announce only at defined milestones (25/50/75/100%), not on every stream event, to avoid announcement flooding
Semantics tree updates during upload must not cause full widget rebuilds — use a RepaintBoundary or targeted BlocBuilder scoped to the progress section
security requirements
Announcements must not include file paths, storage keys, or internal IDs — use only human-readable file names
ui components
Semantics on source option ListTiles
Semantics on preview row ListTiles
Semantics on remove IconButton per row
Semantics on Confirm and Cancel AppButtons
SizedBox 44x44 touch target enforcement throughout

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

The upload progress announcement throttling is the trickiest part — maintain a _lastAnnouncedMilestone int in the BLoC and only call SemanticsService.announce when the current milestone bucket (floor(percent / 25) * 25) is greater than _lastAnnouncedMilestone. This prevents announcement spam on fast connections while still giving meaningful progress feedback. For screen reader navigation order in the preview list, use MergeSemantics or an explicit Semantics traversal order (via sortKey) to ensure VoiceOver reads thumbnail → filename → size → remove button in that order per row. Given this project's strong accessibility mandate (Blindeforbundet users with screen readers, NHF cognitive accessibility requirements), add a comment block above the Semantics annotations explaining the intent so future developers do not inadvertently remove them.

Testing Requirements

Widget tests using flutter_test: (1) render picker and assert both source option semantic labels are present, (2) inject two PendingAttachment items and assert each preview row has the expected semantic label, (3) simulate a MIME rejection event from the BLoC and assert SemanticsService.announce was called with the rejection message, (4) simulate upload progress events at 25%, 50%, 75%, 100% and assert announce is called exactly 4 times with the correct messages, (5) simulate limit-exceeded state and assert the error message announce fires once. Use a MockSemanticsService. Manual TalkBack (Android) and VoiceOver (iOS) tests on TestFlight to confirm all announcements are intelligible and navigation order is logical.

Component
Attachment Picker UI
ui medium
Epic Risks (4)
medium impact medium prob dependency

Flutter does not include a first-party PDF renderer. Third-party packages (e.g., flutter_pdfview, syncfusion_flutter_pdf) have inconsistent accessibility support, may not honour dynamic type scaling, and can introduce large binary size increases. Choosing the wrong package late in development risks rework or an inaccessible PDF experience for Blindeforbundet users.

Mitigation & Contingency

Mitigation: Evaluate and spike two PDF rendering packages (flutter_pdfview and pdfx) in the first task of this epic before committing to implementation. Criteria: VoiceOver compatibility, dynamic type support, APK/IPA size delta, and licence. Document the decision in an ADR.

Contingency: If no package meets accessibility requirements, fall back to opening PDFs in the system browser via url_launcher, which inherits the OS's accessible PDF viewer. Display a clear 'Opening in external viewer' message to set user expectation.

medium impact medium prob technical

Flutter's Semantics API for live region announcements (analogous to aria-live) has known gaps on Android for dynamic content updates. Upload progress announcements required by the accessibility user story may not fire reliably on Android devices used by HLF and NHF members.

Mitigation & Contingency

Mitigation: Use SemanticsService.announce() for imperative announcements at each upload state transition rather than relying on declarative Semantics widget tree updates. Test on a physical Android device with TalkBack enabled during development, not only on iOS with VoiceOver.

Contingency: If SemanticsService.announce() proves unreliable, implement a persistent accessible status banner at the top of the screen that reflects the current upload state as plain text, satisfying the WCAG success criterion through a visual + programmatic mechanism.

low impact high prob technical

iOS and Android file picker behaviour diverges significantly: MIME type filtering works reliably on Android but is advisory on iOS (users can still navigate to and select non-compliant files via the Files app). This could allow unsupported file types to reach the upload service, causing validation failures with a confusing user experience.

Mitigation & Contingency

Mitigation: Rely on the AttachmentUploadService (Epic 2) as the authoritative MIME validation gate, regardless of platform. In the UI, re-validate the picked file's extension/MIME after selection and show an inline plain-language error ('Only PDF, JPEG, and PNG files are supported') before even calling the service.

Contingency: If users consistently hit the error due to iOS file picker limitations, add a user-facing help tooltip on the picker sheet explaining the supported file types, reducing support volume while the underlying OS limitation persists.

high impact medium prob scope

WCAG 2.2 AA compliance for three new interactive components (picker, grid, modal) with file system integration, role-gated actions, and live regions is a significant accessibility surface area. An incomplete audit before release could result in the feature being unusable for Blindeforbundet screen reader users at launch.

Mitigation & Contingency

Mitigation: Allocate a dedicated accessibility audit task at the end of the epic using the accessibility-test-harness (619) and wcag-compliance-checker (620) components already in the project. Include at least one manual test session on a physical device with VoiceOver enabled. Fail the epic's definition of done if any WCAG 2.2 AA violation remains open.

Contingency: If critical accessibility issues are found late, gate the feature behind the org-level attachments_enabled feature flag and only enable it for NHF (the requesting org) after a targeted fix cycle, rather than delaying the release for all organisations.