high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Widget renders a scrollable ListView of PeriodPreset items when the presets list is non-empty
Each list item displays the preset name in the primary text style and the formatted date range (e.g. '1 Jan 2025 – 31 Dec 2025') in the secondary text style
The currently selected preset is visually distinguished (e.g. highlighted background or checkmark) using design token colours
Tapping a list item invokes the onSelected(PeriodPreset) callback with the correct preset
An empty-state widget is rendered when the presets list is empty, with a descriptive message
Touch targets are at least 48 × 48 dp in compliance with WCAG 2.2 AA
All text elements meet WCAG 2.2 AA contrast ratio (minimum 4.5:1 for normal text)
The widget accepts a nullable selectedPresetId and highlights the matching item
The widget is a pure stateless widget; state management is handled by the caller via Riverpod
Screen reader users can identify each list item by its preset name and date range via Semantics widget

Technical Requirements

frameworks
Flutter
Riverpod
data models
PeriodPreset
performance requirements
ListView must use itemBuilder (lazy rendering) to handle lists of 50+ presets without jank
Widget rebuild must be limited to the list item whose selection state changes
security requirements
Preset names must be rendered as plain text, not parsed as HTML or rich text, to prevent injection
ui components
PeriodPresetList (stateless wrapper)
PeriodPresetListItem (individual row widget)
PeriodPresetEmptyState (empty state placeholder)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement as a StatelessWidget accepting List presets, String? selectedPresetId, and ValueChanged onSelected. Use ListView.builder for lazy rendering. Extract PeriodPresetListItem as a separate StatelessWidget to keep the build method clean and enable targeted widget tests.

Apply design token colours from the project's token system for background, text, and selected highlight — never hardcode colour values. Wrap each item in a Semantics widget with label set to '${preset.name}, ${formattedRange}'. Use a minimum height constraint of 48.0 on each item via ConstrainedBox or SizedBox. Format date ranges using the intl package's DateFormat, consistent with locale settings used elsewhere in the app.

Testing Requirements

Widget tests using flutter_test. Test cases: (1) renders correct number of items from a non-empty list, (2) empty state is shown when list is empty, (3) selected item is visually highlighted when selectedPresetId matches, (4) onSelected callback fires with the correct PeriodPreset when an item is tapped, (5) Semantics widget wraps each item with the preset name as the label, (6) touch target size is at least 48 dp (verify via tester.getSize). Use goldenToolkit or flutter_test screenshot assertions for visual regression on selected vs unselected states.

Component
Period Preset List Widget
ui low
Epic Risks (3)
high impact medium prob security

Supabase RLS policies for period preset configuration may be missing or incorrectly scoped, causing one organisation's presets to leak to another or write operations to fail silently.

Mitigation & Contingency

Mitigation: Define and review RLS policies for the bufdir_period_presets table in the migration file before any repository code is written. Include an integration test that verifies cross-organisation isolation using two distinct org credentials.

Contingency: If RLS is misconfigured in production, immediately disable the period preset fetch endpoint and fall back to hardcoded global presets until the policy is corrected and redeployed.

medium impact medium prob technical

The activities table may lack a composite index on (organisation_id, activity_date), causing the range count query in BufdirAggregationRepository to perform a full table scan and exceed acceptable response time for large organisations.

Mitigation & Contingency

Mitigation: Add a migration that creates a composite index on (organisation_id, activity_date) as part of this epic. Benchmark the count query against a representative dataset (10 000+ rows) before marking the epic complete.

Contingency: If query latency is unacceptable after indexing, move the count query to a Supabase RPC function that leverages a materialised view or partial index, accepting a slight staleness window.

medium impact medium prob technical

Flutter's native date picker widgets have known accessibility gaps (missing semantic labels, non-standard focus traversal) that may prevent WCAG 2.2 AA compliance out of the box, requiring a custom implementation.

Mitigation & Contingency

Mitigation: Evaluate third-party accessible date picker packages (e.g., table_calendar with custom semantics) against WCAG 2.2 AA criteria before beginning implementation. Document the chosen approach in the epic kick-off.

Contingency: If no package meets accessibility requirements, implement a simple text-field-based date entry with explicit semantic labels and format hints as an accessible fallback, deferring a fully visual calendar to a later iteration.