critical priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

ExportPreviewPanel is a StatelessWidget that receives an ExportPreviewCubit via BlocProvider and rebuilds on state changes
When state is ExportPreviewLoading, a skeleton loader with at least 5 shimmer rows is displayed
When state is ExportPreviewLoaded, a scrollable table renders all BufdirCategoryRow entries with columns: Category Name, Activity Count, Participant Count
Column headers use design token typography (e.g. AppTextStyle.labelMedium) and are visually distinct from row content
Each data row is wrapped in a Semantics widget with label: '<categoryName>: <activityCount> activities, <participantCount> participants'
The table is wrapped in a SingleChildScrollView with vertical scroll axis
When the category list is empty, an empty-state message is displayed: 'No activities found for the selected scope and period'
When state is ExportPreviewError, an error banner with the user-facing error message is shown
All colours, spacing, and typography values come exclusively from the design token system — no hardcoded values
Widget renders correctly at font scale factors 1.0, 1.5, and 2.0 (verified in golden tests)
No overflow errors occur with category names up to 80 characters

Technical Requirements

frameworks
Flutter
flutter_bloc
BLoC
data models
activity_type
bufdir_column_schema
performance requirements
Table must render 50 rows without frame drops below 60 fps on mid-range devices
Skeleton loader must appear within one frame of entering ExportPreviewLoading state
security requirements
No raw data from the DTO is rendered directly as HTML or rich text — use plain Text widgets only to prevent injection
ui components
ExportPreviewPanel (top-level widget)
BufdirCategoryTableHeader (column header row)
BufdirCategoryTableRow (data row with Semantics)
ExportPreviewSkeleton (shimmer skeleton loader)
ExportPreviewEmptyState
ExportPreviewErrorBanner

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use a CustomScrollView with SliverList for the table body to allow the metadata header (task-004) and warning section (task-005) to be added as additional slivers without restructuring the widget tree. Implement the skeleton loader using a shimmer animation package already in the project, or a simple AnimatedContainer with a gradient sweep — do not add new dependencies without team approval. Design token usage: import from the project's AppColors, AppSpacing, and AppTextStyle constants. For long category names, use TextOverflow.ellipsis with a Tooltip to show the full name on long-press.

Keep ExportPreviewPanel free of business logic; all data transformations happen in the Cubit/mapper layer.

Testing Requirements

Widget tests using flutter_test: render in ExportPreviewLoading state and verify skeleton rows are present; render in ExportPreviewLoaded with 3 rows and verify each row's text content and Semantics label; render with an empty category list and verify empty-state message; render ExportPreviewError and verify error banner text. Golden tests at font scales 1.0 and 2.0. Accessibility test using flutter_test's SemanticsController to confirm each row has a non-empty Semantics label. Use BlocProvider with a MockCubit in all widget tests.

Component
Export Preview Panel
ui medium
Epic Risks (2)
medium impact medium prob technical

The preview panel requires a round-trip to the edge function to compute aggregated counts. If this call takes 5–15 seconds for large scopes, users may assume the app has frozen and navigate away, potentially triggering duplicate preview requests or leaving the export in an undefined state.

Mitigation & Contingency

Mitigation: Show an immediate skeleton loading state on the preview panel as soon as the period and scope are confirmed, with named stage labels (e.g. 'Querying activities…', 'Computing totals…') streamed from the edge function's progress events. Set a clear user-visible timeout with a retry option. Pre-warm the edge function with a lightweight ping when the scope selector is opened.

Contingency: If preview latency consistently exceeds 10 seconds for large scopes, cache the preview payload in local BLoC state and allow the coordinator to proceed to confirmation without re-fetching if scope and period have not changed since the last preview.

high impact medium prob scope

The export preview panel contains dynamic content (warning badges, loading skeletons, aggregated counts) that must be announced correctly by VoiceOver and TalkBack. Incorrect semantics annotations could make the preview unreadable for blind coordinators, violating the project's WCAG 2.2 AA accessibility mandate.

Mitigation & Contingency

Mitigation: Implement semantic annotations using Flutter's Semantics widget with explicit labels for all dynamic content. Use live region announcements for loading state transitions. Schedule a dedicated accessibility review session with a screen reader user (Blindeforbundet has relevant expertise) before marking the epic complete.

Contingency: If the preview panel cannot be made fully screen-reader-accessible in time for launch, ship a simplified text-only summary mode activated by a toggle at the top of the preview panel, which renders all data as a single readable paragraph with no interactive badges.