high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

ExportScopeSelector widget reads available scopes from the Riverpod exportScreenProvider and renders only scopes the current user is permitted to access
Selecting a scope calls exportScreenProvider.notifier.selectScope(scope) which updates the BufdirExportRequest in state
The export action button's enabled state is computed as: scope != null AND period != null — evaluated reactively in the provider
When scope is null or period is null, the export button is rendered with design token color tokens.button.disabled and pointer events disabled
Scope-access errors (PermissionDenied, ScopeUnavailable) are surfaced as an inline error banner above the selector using design token error colour
The inline error banner uses the error colour token (not hardcoded hex) and is announced by screen reader (semanticsLabel set)
Selecting a new scope clears any previously displayed scope-access error
The BufdirExportRequest in the provider state reflects the latest scope selection at all times without stale values
Widget tests confirm: button disabled with no scope, button disabled with scope but no period, button enabled with both, error banner appears on PermissionDenied

Technical Requirements

frameworks
Flutter
Riverpod
data models
bufdir_export_audit_log
performance requirements
Scope list renders without jank for up to 50 scope entries
State update on scope selection must reflect in UI within one frame (no unnecessary rebuilds)
security requirements
Scope list must be filtered server-side via RLS — client must not rely solely on UI filtering to prevent unauthorised scope access
User role verified from JWT claim before rendering scope selector — no client-side role spoofing possible
ui components
ExportScopeSelector
InlineErrorBanner
AppButton (disabled state)
Design token: tokens.button.disabled, tokens.color.error

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Model the export screen state as a Riverpod StateNotifierProvider or AsyncNotifierProvider with a BufdirExportScreenState freezed class containing: selectedScope, selectedPeriod, exportRequest (derived), scopeError. Use a computed getter `bool get canExport => selectedScope != null && selectedPeriod != null` on the state class to drive button enabled state. In the widget, use `ref.watch(exportScreenProvider.select((s) => s.canExport))` for fine-grained rebuilds rather than watching the whole state. For the error banner, create a reusable InlineErrorBanner widget that accepts a String?

message and uses AppTheme.of(context).errorColor from design tokens — do not hardcode colours. Ensure ExportScopeSelector passes scope selection up via a callback rather than managing state internally to keep it a pure presentational widget.

Testing Requirements

Widget tests with flutter_test and ProviderScope override: (1) renders only permitted scopes for a coordinator role, (2) renders all org scopes for org admin role, (3) export button disabled when scope is null, (4) export button disabled when period is null, (5) export button enabled when both scope and period are non-null, (6) PermissionDenied error surfaced as InlineErrorBanner with correct error token colour, (7) selecting new scope clears existing error banner, (8) provider state contains correct scope after selection. Accessibility: assert semanticsLabel is present on error banner.

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.