high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

AttachmentThumbnailGrid renders a 2-column responsive grid of attachment thumbnails inside the activity detail view
JPEG and PNG attachments display an image preview using the file's local path or cached network image
PDF attachments display a generic document icon (e.g. Icons.picture_as_pdf or an asset SVG) instead of an image preview
Each thumbnail cell shows the file name truncated to one line with ellipsis and the file size formatted as KB or MB below the thumbnail
Empty state is rendered when the activity has no attachments (e.g. a subtle 'No attachments' label)
Loading state is rendered while the repository fetch is in progress (e.g. shimmer or circular indicator per cell)
Error state is rendered when the repository fetch fails (e.g. icon + retry button)
Widget accepts an activityId parameter and fetches attachments from ActivityAttachmentRepository
All text elements pass WCAG 2.2 AA contrast ratio (minimum 4.5:1 on dark and light backgrounds)
Widget is accessible: all interactive elements have semantic labels for screen readers (VoiceOver/TalkBack)

Technical Requirements

frameworks
Flutter
Riverpod or BLoC for state management
apis
ActivityAttachmentRepository.getAttachmentsForActivity(activityId)
data models
ActivityAttachment
AttachmentMetadata
performance requirements
Thumbnail images must be loaded asynchronously and cached (use cached_network_image or Flutter's ImageProvider with cache)
Grid must render first frame in under 16 ms on mid-range device (no blocking I/O on main thread)
security requirements
Do not expose raw Supabase storage URLs in widget tree — use signed URLs from AttachmentSignedUrlService
File names displayed must be HTML-safe (no script injection via file names)
ui components
GridView.builder (2-column SliverGridDelegateWithFixedCrossAxisCount)
CachedNetworkImage or Image widget for previews
Icon widget for PDF document icon
Text widget for file name and size
CircularProgressIndicator or shimmer for loading state

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use a 2-column GridView.builder with childAspectRatio around 0.85 to give space for the subtitle. Determine rendering strategy from attachment.mimeType: if 'image/jpeg' or 'image/png', use CachedNetworkImage (or Image.network) with a BoxFit.cover; if 'application/pdf', show a styled container with an icon. Format file size using a utility: bytes < 1024 → 'X B', < 1048576 → 'X KB', else 'X MB'. Truncate file names with overflow: TextOverflow.ellipsis and maxLines: 1.

Wrap the grid in a Consumer (Riverpod) or BlocBuilder to handle loading/error/data states. Follow the project's design token system for spacing, typography, and color — do not hardcode values. Apply Semantics wrapper to each thumbnail cell with a label combining file name and type.

Testing Requirements

Write widget tests using flutter_test and WidgetTester. Test: grid renders correct number of cells for a list of 3 mock attachments; JPEG attachment shows Image widget; PDF attachment shows document Icon widget; empty list renders empty state widget; loading state renders progress indicator. Use a mocked ActivityAttachmentRepository via Riverpod override or BLoC stub. Accessibility test: verify Semantics labels are present on thumbnail cells.

Component
Attachment Thumbnail Grid
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.