critical priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

The pinch-to-zoom InteractiveViewer area is wrapped in a Semantics widget with label: 'Preview of ${fileName}. Pinch to zoom.' and onTap/onLongPress excluded if not meaningful
Close button has semantic label: 'Close preview' and is keyboard-focusable
Share/download button has semantic label: 'Share ${fileName}' and is keyboard-focusable
When the modal transitions from loading to content-loaded state, SemanticsService.announce('${fileName} loaded', TextDirection.ltr) fires once
When the modal transitions to an error state, SemanticsService.announce('Could not load ${fileName}. ${plainLanguageReason}', TextDirection.ltr) fires once
The loading indicator has a Semantics label: 'Loading attachment, please wait'
Error state displays a plain-language message (e.g., 'This file could not be opened. Please check your connection and try again.') with no technical error codes visible to the user
All interactive buttons meet the 44x44 pt minimum touch target requirement
Widget test confirms semantic labels are present on the pinch-to-zoom area, close button, and share button

Technical Requirements

frameworks
Flutter
apis
SemanticsService.announce
Flutter Semantics widget
Flutter InteractiveViewer
data models
Attachment (file_name, mime_type)
performance requirements
SemanticsService.announce must be called at most once per state transition — guard with a flag or BLoC state comparison to prevent re-announcement on rebuilds
security requirements
Error messages shown to users must not contain raw exception messages, stack traces, or storage bucket paths
ui components
Semantics on InteractiveViewer
Semantics on close IconButton
Semantics on share IconButton
Semantics on loading CircularProgressIndicator
Plain-language error Text widget

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use a BlocListener to watch for state changes and call SemanticsService.announce inside the listener — this ensures announcements are decoupled from widget rebuilds and fire exactly once per transition. Wrap the InteractiveViewer in a Semantics widget with onPanUpdate and onScale excluded (set to null) since these gestures are not meaningful for screen reader users and only cause confusion. For the plain-language error messages, define them as constants in a localization file rather than inline strings so they can be updated without code changes. Ensure the modal's focus is moved to the close button when it opens (use FocusScope.of(context).requestFocus) so screen reader users land in the correct place.

Testing Requirements

Widget tests using flutter_test: (1) render modal in loading state and assert loading Semantics label is present, (2) transition to loaded state and assert SemanticsService.announce was called with the loaded message, (3) transition to error state and assert announce was called with the error message and plain-language error Text is visible, (4) assert close button and share button semantic labels match spec. Use a MockSemanticsService or capture announce calls via a listener. Manual VoiceOver test on iOS TestFlight device: navigate through all three modal states and confirm announcements are heard at the correct times.

Component
Attachment Preview Modal
ui low
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.