critical priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

ExportScopeSelector is a ConsumerWidget (Riverpod) that reads exportScopeProvider and renders exactly three scope cards: Local Chapter, Region, National
Each card shows: scope title (translated to Norwegian display name), a brief description of what that scope covers, and a visual selected/unselected state
Cards for scopes NOT in permittedScopes list are rendered with disabled styling: reduced opacity (≤0.4), no tap interaction, and a lock icon or 'Not available' label
Tapping an enabled card calls selectScope() on the notifier and immediately reflects the selected state without additional user action
Selected card has a visually distinct border or background using design token colours (not hardcoded hex values)
WCAG 2.2 AA contrast ratio of at least 4.5:1 is met for all text elements in both selected and unselected states — verified with a contrast checker
Each card has a Semantics widget wrapping with label, hint, and selected properties correctly set for screen readers (VoiceOver and TalkBack)
Disabled cards have Semantics with enabled: false so screen readers announce them as unavailable
Widget handles the isLoading state from ExportScopeState by showing a shimmer or skeleton loading placeholder instead of cards
Widget handles empty permittedScopes (all disabled) and shows an informative empty-state message: 'You do not have export access. Contact your administrator.'
Widget tests verify: all three cards render, disabled card is not tappable, selection updates provider state, loading state shows skeleton, empty-state message appears when no scopes permitted

Technical Requirements

frameworks
Flutter
Riverpod (flutter_riverpod)
Design token system (colours, typography, spacing, radii)
data models
ExportScope
ExportScopeState
performance requirements
Widget must rebuild only when ExportScopeState changes — use ref.watch with select() to avoid over-rebuilding
Card tap response must be instantaneous (< 16ms state update)
security requirements
Disabled cards must be truly non-interactive — not just visually disabled. Use IgnorePointer or AbsorbPointer to prevent tap events on disabled cards
Do not expose the full permittedScopes list in widget debug labels or error messages — scope eligibility is access-sensitive
ui components
ExportScopeCard (private sub-widget)
ScopeLoadingSkeleton (loading state)
ScopeEmptyState (no-access state)
Semantics wrapper

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Follow the existing card widget patterns in the project (AppButton, contact cards) for structural consistency. Use the design token system for all colours — no hardcoded hex values. Norwegian display names for scope cards: 'Lokalt lokallag' (localChapter), 'Region' (region), 'Nasjonalt' (national). For accessibility, pay particular attention to the Blindeforbundet requirement: screen reader support is critical for this app.

Test with VoiceOver on a physical iOS device or simulator during development, not just automated checks. The widget should be placed at lib/features/bufdir_export/widgets/export_scope_selector.dart. Consider extracting ExportScopeCard as a separate file for testability.

Testing Requirements

Write widget tests using flutter_test WidgetTester. Tests must cover: (1) all three scope cards render with correct titles, (2) disabled card does not fire selectScope when tapped, (3) tapping an enabled card updates the Riverpod provider state, (4) isLoading=true shows skeleton, not cards, (5) empty permittedScopes shows the no-access message, (6) selected card has correct visual state (use findsOneWidget with semantic label check). Use ProviderScope with override in tests to inject pre-set ExportScopeState values. Run flutter_test with accessibility checks using AccessibilityGuidelines if available in the project.

Aim for ≥80% widget code coverage.

Component
Export Scope Selector
ui medium
Epic Risks (2)
high impact medium prob security

The scope selector must accurately reflect each coordinator's access rights within the org hierarchy. If a coordinator can select a scope broader than their authorised access, the edge function's RLS enforcement must catch the attempt — but a permissive RLS policy or a bug in the scope resolver could allow unauthorised data to be exported.

Mitigation & Contingency

Mitigation: Implement permission enforcement at two independent layers: (1) the scope selector only renders options permitted by the user's role record, and (2) the edge function re-validates the requested scope against the user's JWT claims before executing any queries. Write integration tests that attempt to invoke the edge function with a scope beyond the user's permissions and assert rejection.

Contingency: If a permission bypass is discovered post-launch, immediately disable the export feature via the org-level feature flag while the fix is deployed. Review all audit records for exports that may have included out-of-scope data and notify affected organisations.

medium impact medium prob technical

The export workflow has 7+ discrete states (idle, scope selected, period selected, preview loading, preview ready, confirming, exporting, complete, failed) and several conditional transitions. An incomplete BLoC state machine could allow duplicate submissions, stale preview data to be confirmed, or error states to be unrecoverable without a restart.

Mitigation & Contingency

Mitigation: Model the state machine explicitly as a sealed class hierarchy before coding. Review the state diagram against all user story acceptance criteria. Write bloc unit tests for every valid and invalid state transition, including the happy path and all documented error states.

Contingency: If the BLoC grows too complex to test reliably, decompose it into two cooperating blocs: one for configuration (scope + period selection) and one for execution (preview + confirm + export), linked by a coordinator object.