high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

ExportStep enum defines exactly 5 steps: queryingData, aggregating, generatingFile, uploading, ready — plus an error variant
Widget is declared as StatelessWidget and accepts ExportStep currentStep and optional double? percentage parameters
Animated progress bar advances smoothly between steps using AnimatedContainer or TweenAnimationBuilder — no jumpy transitions
Each step label is rendered inline with the progress bar; the active step is visually distinct (color, weight) from completed and pending steps
Error state renders a descriptive message and a retry button; tapping retry invokes the provided VoidCallback? onRetry
All interactive elements and progress states expose Semantics labels readable by VoiceOver/TalkBack (e.g., 'Export step 2 of 5: Aggregating data, 40%')
Percentage value, when provided, is displayed numerically and reflected in the progress bar fill
Widget renders correctly on screen widths from 320 px to 428 px without overflow
Widget uses only design token colors and typography — no hardcoded hex values or font sizes
Widget can be rendered in a Flutter widget test with no platform channel calls or Supabase dependencies
Golden test or visual snapshot passes for each of the 6 states: queryingData, aggregating, generatingFile, uploading, ready, error

Technical Requirements

frameworks
Flutter
flutter_test
data models
ExportStep (enum)
performance requirements
Animation runs at 60 fps on mid-range Android devices
Widget build time under 4 ms on profile builds
security requirements
No PII displayed in progress labels
onRetry callback must not be invoked automatically — only on explicit user tap
ui components
AnimatedContainer or TweenAnimationBuilder for progress bar
Row of step label widgets with conditional styling
Semantics wrapper for screen-reader announcements
AppButton (from shared widget library) for retry action
Design token color and typography constants

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Declare ExportStep as a Dart enum in a shared types file so both the widget and the BLoC/Riverpod state can import it without circular dependencies. Use implicit animations (AnimatedContainer width driven by a 0.0–1.0 fraction computed from currentStep.index / (ExportStep.values.length - 1)) to avoid managing AnimationController in a StatelessWidget. For the step labels, use a ListView.builder or a Row with Expanded children — not hardcoded widths. Apply design tokens via Theme.of(context).extension() or a static const accessor; never import raw color values.

Ensure the Semantics node uses liveRegion: true so screen readers announce state changes without user focus. The retry button should only appear in the error state; guard with a conditional in the build method rather than opacity tricks to avoid accessibility pitfalls.

Testing Requirements

Write flutter_test widget tests covering all 6 ExportStep states (including error). Verify Semantics tree contains correct label strings for each state. Pump animation frames and assert progress bar width matches expected fraction. Test that onRetry is called exactly once when retry button is tapped.

Test with percentage = null and percentage = 0.0/0.5/1.0. Run golden tests for visual regression. Target 100% branch coverage of the widget's build method.

Component
Export Progress Indicator
ui low
Epic Risks (3)
high impact medium prob technical

NHF's three-level hierarchy (national / region / chapter) with 1,400 chapters may have edge cases such as chapters belonging to multiple regions, orphaned nodes, or missing parent links in the database. Incorrect scope expansion would silently under- or over-report activities, which could invalidate a Bufdir submission.

Mitigation & Contingency

Mitigation: Obtain a full hierarchy fixture export from NHF before implementation begins. Write exhaustive unit tests covering boundary cases: single chapter, full national roll-up, chapters with no activities, and chapters assigned to multiple regions. Validate resolver output against a known-good manual count.

Contingency: If hierarchy data quality is too poor for automated resolution at launch, implement a manual scope override in the coordinator UI that allows the coordinator to explicitly select org units from a tree picker, bypassing the resolver.

medium impact high prob dependency

The activity_type_configuration table may not cover all activity types currently in use, leaving a subset unmapped at launch. Bufdir submissions with unmapped categories will be incomplete and may be rejected by Bufdir.

Mitigation & Contingency

Mitigation: Run a query against production activity data before implementation to enumerate all distinct activity type IDs. Cross-reference with Bufdir's published category schema (request from Norse Digital Products). Flag every gap as a known issue and build the warning surface into the preview panel.

Contingency: Implement a fallback 'Other' category bucket for unmapped types and surface a prominent warning in the export preview requiring coordinator acknowledgement before proceeding. Log unmapped types for post-launch cleanup.

high impact low prob security

Supabase RLS policies on generated_reports and the storage bucket must enforce strict org isolation. A misconfigured policy could allow a coordinator from one organisation to read another organisation's export files, creating a serious data breach with GDPR implications.

Mitigation & Contingency

Mitigation: Write RLS integration tests that attempt cross-org reads with explicitly different JWT tokens and assert that all attempts return empty sets or 403 errors. Include RLS policy review in the pull request checklist. Use Supabase's built-in policy tester during development.

Contingency: If a policy gap is discovered post-deployment, immediately revoke all signed URLs for affected exports, audit the access log for unauthorised reads, and issue a coordinated disclosure to affected organisations per GDPR breach notification requirements.