high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

Before opening the picker, the existing attachment count for the activity is fetched from the attachment-upload-service (or BLoC state)
The picker sheet displays a count indicator such as '3 of 5 attachments' at the top of the sheet
When existingCount + selectedCount >= 5, the 'Confirm' button is disabled and an inline message 'Maximum 5 attachments reached' is displayed below the count indicator
The count indicator and error message update in real time as the user adds or removes files from the pending selection list
If the user attempts to select more files than the remaining slots allow, only the first N files (up to the remaining quota) are added and a plain-language message explains the truncation
The 'Confirm' button remains active and the error message is absent when the total count is below 5
Widget test verifies the button is disabled and error message is visible when total count equals 5
Widget test verifies the button is enabled and error message is absent when total count is 4

Technical Requirements

frameworks
Flutter
BLoC
apis
attachment-upload-service getAttachmentCount(activityId)
data models
Activity (id)
Attachment (activity_id)
performance requirements
Existing count should be passed into the picker as a constructor parameter or BLoC state — do not make a network call from inside the picker widget
Count UI updates must be synchronous from BLoC state, no async re-renders on count changes
security requirements
The 5-attachment limit must also be enforced server-side in the Supabase RLS policy or edge function — the client-side check is UX only
ui components
Text count indicator ('X of 5 attachments')
Inline error Text widget with error styling
Disabled state for AppButton Confirm

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

The cleanest approach is to pass existingCount as a required int parameter to the AttachmentPickerUI widget. Inside the widget's BLoC/Cubit, compute remainingSlots = 5 - existingCount - pendingSelections.length and expose an isAtLimit getter. The 'Confirm' button should read isAtLimit from the state and set onPressed to null when true (Flutter's standard pattern for disabling buttons). Use design tokens for error text color to stay consistent with the app's validation styling.

The plain-language message should avoid technical jargon per the project's cognitive accessibility principle — use 'You can add N more attachments' rather than 'Limit exceeded'.

Testing Requirements

Widget tests using flutter_test: (1) inject existingCount=4 and select 1 file — assert button enabled and no error shown, (2) inject existingCount=4 and select 2 files — assert only 1 file added, button disabled, truncation message shown, (3) inject existingCount=5 — assert picker opens with button immediately disabled and max-reached message visible, (4) inject existingCount=3 and remove 1 pending file — assert count updates and button re-enables if previously at limit. No network calls needed in tests; mock the count via BLoC initial state.

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.