critical priority low complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

PeriodSelectionScreen is a StatelessWidget that wraps its content in a BlocProvider<PeriodSelectionBloc>
BlocProvider creates a fresh PeriodSelectionBloc instance using context.read or dependency injection — not created inline if DI is in use
Screen reads currentUserRoleProvider via ConsumerWidget or Consumer; if role is neither 'coordinator' nor 'organisation_admin', it renders NoAccessScreen inline (not via navigation push) to avoid router stack issues
Allowed roles (coordinator, organisation_admin) see the full screen content scaffold
Screen has a top AppBar using the project's shared page header widget with back-button wired to Navigator.pop or router.pop
Screen title text uses AppTextStyles.headingMedium (or equivalent design token) — no hardcoded font size or weight
Screen passes widget tests with role=coordinator showing full scaffold and role=peer_mentor showing NoAccessScreen
Screen is registered in the app router with the correct route path for Bufdir export flow
No inline style blocks or hardcoded colors — all visual values reference design tokens

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
data models
currentUserRoleProvider
UserRole enum
PeriodSelectionBloc
performance requirements
Role check is synchronous read from Riverpod cache — no loading state needed at scaffold level
BlocProvider.create must not trigger heavy initialization; defer data fetching to BLoC events
security requirements
Role guard must be enforced at widget level AND at router level — widget-level alone is insufficient if deep-links are possible
Do not expose Bufdir data to unauthorized roles even transiently during build
ui components
BlocProvider wrapping the screen body
ConsumerWidget or Consumer for role check
NoAccessScreen (existing shared widget)
AppBar / page header widget from shared component library
Design token typography for screen title

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Integration Task

Handles integration between different epics or system components. Requires coordination across multiple development streams.

Implementation Notes

Extend ConsumerWidget (not StatelessWidget directly) so Riverpod's ref is available without a separate Consumer wrapper at the top level. Pattern: `class PeriodSelectionScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final role = ref.watch(currentUserRoleProvider); if (role != UserRole.coordinator && role != UserRole.organisationAdmin) return const NoAccessScreen(); return BlocProvider(create: (_) => PeriodSelectionBloc(), child: const _PeriodSelectionScreenBody()); } }`. Keep the inner body as a private StatelessWidget `_PeriodSelectionScreenBody` so BlocProvider is above the widget that uses the BLoC. This avoids calling context.read() before BlocProvider is in the tree.

Route registration: add a GoRoute or equivalent with path '/bufdir/period-selection' guarded at router level too.

Testing Requirements

Widget tests: (1) role=coordinator renders scaffold (not NoAccessScreen); (2) role=organisation_admin renders scaffold; (3) role=peer_mentor renders NoAccessScreen; (4) role=admin renders NoAccessScreen; (5) AppBar back button calls Navigator.pop. Use ProviderScope with override for currentUserRoleProvider. Wrap in MaterialApp for proper Navigator context. No integration tests at this task level — covered in task-011.

Component
Period Selection Screen
ui low
Epic Risks (3)
medium impact medium prob technical

The record count query is asynchronous and may take up to 1 second on a slow connection. If the BLoC does not manage loading states carefully, the UI may show a stale count or the confirm button may be briefly enabled during the loading transition, allowing premature submission.

Mitigation & Contingency

Mitigation: Define explicit BLoC states: PeriodSelectionLoading, PeriodSelectionCountLoaded, PeriodSelectionValidationError, and PeriodSelectionReady. The confirm button must only be enabled in PeriodSelectionReady. Add a debounce of 300ms on period-change events before triggering the count query to prevent excessive calls.

Contingency: If debouncing is insufficient to prevent UX degradation on slow connections, add an optimistic loading skeleton to the RecordCountBanner that clearly communicates a pending state, and keep the confirm button disabled until the count resolves.

high impact low prob integration

The confirmedReportPeriodProvider Riverpod contract between this feature and the Bufdir Export feature may not be defined yet, causing integration failures when the downstream export screen attempts to read the provider.

Mitigation & Contingency

Mitigation: Define the confirmedReportPeriodProvider as a StateProvider<DateTimeRange?> in a shared providers file at the start of this epic, before any screen code is written. Communicate the provider contract to the Bufdir Export feature team so both sides align on the same provider reference.

Contingency: If the export feature consumes the period through a different mechanism (e.g., navigation arguments), add a thin adapter that writes the confirmed period to both the Riverpod provider and the navigation argument, ensuring backward compatibility.

medium impact medium prob technical

Flutter's live region support for screen readers is inconsistent across platforms (iOS VoiceOver vs Android TalkBack), and the record count banner must be announced on every change — a pattern that has historically required platform-channel workarounds.

Mitigation & Contingency

Mitigation: Test the live region announcement on both iOS (VoiceOver) and Android (TalkBack) early in implementation using the accessibility_test harness. Reference the project's existing live-region-announcer component (664-accessibility-live-region-announcer) for a proven implementation pattern.

Contingency: If native live region support is insufficient, wrap the record count text in a Semantics widget with a programmatically updated label string, and trigger a SemanticsService.announce call on each count update as a platform-agnostic fallback.