high priority low complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

RoleGuard is a ConsumerWidget (Riverpod) that reads the active role from a roleStateProvider — not a hardcoded role check inside AccountingExportScreen
When active role is coordinator or org_admin: the authorisedBuilder callback is rendered (export date picker + trigger button)
When active role is peer_mentor or any other role: the unauthorisedBuilder callback is rendered (read-only history panel header only)
RoleGuard rebuilds automatically when the user switches their active role (e.g., a user with both coordinator and peer_mentor roles switching via the role switcher)
Elements rendered by unauthorisedBuilder do NOT include the export action widgets even as invisible/hidden elements — they are entirely absent from the widget tree
A Semantics(excludeSemantics: true) wrapper is applied around the authorisedBuilder subtree when the role is not permitted, preventing any residual semantic nodes from reaching the screen reader
RoleGuard is reusable: it accepts authorisedRoles as a constructor parameter (List<UserRole>) so it can be applied to other screens with different role requirements
No hardcoded string comparisons for role checks — use the UserRole enum throughout
Guard evaluation is synchronous once roleStateProvider is resolved — no async gaps where the wrong content is briefly shown
Widget renders correctly during the brief loading state of roleStateProvider using a neutral loading placeholder (not a blank screen)

Technical Requirements

frameworks
Flutter
Riverpod
apis
roleStateProvider (Riverpod provider reading active role from Supabase Auth session claims)
performance requirements
Role evaluation must be synchronous after initial provider resolution — no per-frame async checks
Provider rebuild on role switch must complete within one frame (16ms)
security requirements
Role must be derived from the Supabase Auth JWT claims (server-issued) — never from a local state value that the user could manipulate
Guard must treat an unresolved or null role as unauthorised — fail-closed, not fail-open
Role check is defence-in-depth only; the Export Edge Function enforces authorisation server-side independently
ui components
RoleGuard (ConsumerWidget with authorisedRoles, authorisedBuilder, unauthorisedBuilder, loadingBuilder parameters)
Semantics(excludeSemantics: true) wrapper for accessibility compliance

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Implement RoleGuard as a generic reusable widget: RoleGuard({ required List authorisedRoles, required WidgetBuilder authorisedBuilder, required WidgetBuilder unauthorisedBuilder, WidgetBuilder? loadingBuilder }). Inside the widget, use ref.watch(roleStateProvider) which returns AsyncValue; handle AsyncValue.loading, AsyncValue.data, and AsyncValue.error cases. The Semantics(excludeSemantics: true) pattern: when rendering unauthorisedBuilder, also include a Semantics(excludeSemantics: true, child: SizedBox.shrink()) in place of the authorised content to ensure no orphaned semantic nodes.

Do NOT use Visibility(visible: false) or Opacity(opacity: 0) to hide the export action — this still keeps the widget in the semantics tree. The roleStateProvider should be a StateNotifierProvider or NotifierProvider that listens to Supabase Auth state changes and updates the active role whenever the session changes, supporting the multi-role switch feature described in the app architecture.

Testing Requirements

Unit tests: none required (pure widget logic). Widget tests: pump RoleGuard with roleStateProvider overridden to each role value and verify: (1) coordinator role renders authorisedBuilder; (2) org_admin role renders authorisedBuilder; (3) peer_mentor role renders unauthorisedBuilder; (4) null/unresolved role renders loadingBuilder. Semantics test: verify that when unauthorisedBuilder is shown, no semantic nodes from authorisedBuilder exist in the semantics tree (use flutter_test SemanticsHandle). Role switch test: pump with peer_mentor, then update provider to coordinator, pump again, verify authorisedBuilder is now rendered.

Test the guard with RoleGuard used inside AccountingExportScreen by pumping the full screen with different role overrides. Target 95%+ widget test coverage.

Component
Accounting Export Screen
ui medium
Epic Risks (2)
medium impact medium prob technical

Export operations may take several seconds, and the UI must handle all intermediate states (loading, partial success, failure, duplicate warning) without leaving the coordinator on a blank or unresponsive screen. Missing state handling causes confusion and potentially double-submissions.

Mitigation & Contingency

Mitigation: Design the BLoC state machine with explicit states for each transition before writing any widget code: ExportIdle, ExportDuplicateWarning, ExportInProgress, ExportSuccess, ExportPartialSuccess, ExportFailed. Each state maps to a distinct UI. Widget tests cover all states.

Contingency: If a loading state is missed in production, surface a generic error state with a retry action rather than leaving the UI stuck. Add a timeout on the Edge Function call (default 30 seconds) that transitions to ExportFailed with a user-readable message.

high impact medium prob technical

The custom Export Date Range Picker may not be fully navigable with VoiceOver if the underlying Flutter date widgets do not expose the correct semantic tree. This is a critical accessibility failure for Blindeforbundet users who rely on screen readers.

Mitigation & Contingency

Mitigation: Use Flutter's built-in DateRangePicker as the base and wrap with explicit Semantics nodes for start and end labels. Test with VoiceOver on a physical iOS device as part of the definition of done for this component. Reference the existing AccessibilityTestHarness pattern used elsewhere in the app.

Contingency: If the custom picker fails accessibility audit, replace it with two independent DatePicker fields (start and end) using Flutter's standard accessible date input, which has broader VoiceOver support than range variants.