high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Dialog renders all ExportSummary fields: period label, organisation scope name, export format, and estimated record count
Confirm button invokes onConfirm() exactly once when tapped and does not allow double-tap
Cancel button invokes onCancel() and dismisses the dialog without invoking onConfirm
Tapping outside the dialog (barrier dismissal) invokes onCancel()
All text in the dialog meets WCAG 2.2 AA contrast ratio (≥ 4.5:1 for normal text, ≥ 3:1 for large text) against the dialog background
Dialog is fully navigable with VoiceOver (iOS) and TalkBack (Android): every label and action is announced in logical reading order
Confirm button has a distinct accessible label 'Confirm export' and Cancel has 'Cancel export' — not just 'OK' and 'Cancel'
Dialog title is announced as a heading by screen readers (Semantics header: true)
ExportSummary with estimatedRecordCount of 0 shows a warning message 'No records found for the selected period and scope'
Widget is stateless — no setState; all state owned by caller
Widget tests verify label rendering, onConfirm/onCancel callback invocation, and zero-record warning display

Technical Requirements

frameworks
Flutter
flutter_test (widget testing)
Design token system (AppColors, AppSpacing, AppTextStyles)
data models
ExportSummary (period: DateTimeRange, scopeName: String, format: ExportFormat, estimatedRecordCount: int)
performance requirements
Dialog is a pure StatelessWidget — build is O(1) with no async work
No images or heavy assets — text and icon only
security requirements
ExportSummary must not include raw PII — scopeName is the org unit display name only (e.g., 'Oslo Chapter'), not personal data
No network calls or Supabase access inside this widget
ui components
AlertDialog (Flutter Material) or custom Dialog wrapper styled with design tokens
ExportSummaryRow (reusable label+value row widget)
AppButton (existing reusable button from shared widget library) for Confirm and Cancel actions

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement as a StatelessWidget wrapping Flutter's `AlertDialog` with design token styling overrides. Constructor: `const ExportConfirmationDialog({required ExportSummary summary, required VoidCallback onConfirm, required VoidCallback onCancel})`. Use `showDialog(barrierDismissible: true)` from the caller — the widget itself does not call showDialog. Set `barrierDismissible: true` and pass `onCancel` to the dialog's `onBarrierDismissed` equivalent by wrapping in a `PopScope` or by handling `Navigator.pop` in a GestureDetector on the barrier.

Use `Semantics(header: true, child: Text('Confirm Export', style: AppTextStyles.titleLarge))` for the title so screen readers announce it as a heading. Build summary rows as `Row(children: [Text(label, style: AppTextStyles.labelMedium), Text(value)])` using design token colours. The Confirm button should use `AppButton.primary` and Cancel should use `AppButton.ghost` or `AppButton.secondary` from the shared widget library for visual hierarchy. Disable the Confirm button (set `enabled: false`) if `summary.estimatedRecordCount == 0` to prevent confirming an export with no data.

Testing Requirements

Widget tests (flutter_test) covering: (1) all ExportSummary fields render with correct values; (2) tapping Confirm fires onConfirm exactly once; (3) tapping Cancel fires onCancel and not onConfirm; (4) estimatedRecordCount of 0 shows warning text; (5) estimatedRecordCount > 0 does not show warning; (6) dialog title announced as heading via Semantics. Accessibility assertions using flutter_test expectation `tester.getSemantics(find.text('Confirm export'))` confirming button role. Contrast ratio compliance verified manually via the Flutter DevTools accessibility inspector or the Colour Contrast Analyser tool against design token values. Manual device test: navigate entire dialog using VoiceOver on iOS and TalkBack on Android, confirm all elements announced in logical order.

Component
Export Confirmation Dialog
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.