high priority low complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

Dropdown renders all Bufdir categories sourced exclusively from the constants registry — no hardcoded strings in the widget
Human-readable label is displayed in the field; the enum key is stored in form state (not the label string)
Field is marked as required; submitting without a selection shows an inline validation error message
Helper text 'This mapping drives the automated year-end Bufdir export' is visible below the dropdown at all times
Dropdown supports search/filter: typing at least 2 characters filters the list to matching labels
On selection change, a screen reader announcement is triggered (Semantics widget with liveRegion or equivalent)
VoiceOver / TalkBack correctly announces the field label, current value, and available options
Dropdown respects the design token focus ring and contrast ratio (≥ 4.5:1 per WCAG 2.2 AA)
When the form is opened in edit mode, the dropdown is pre-populated with the existing enum key mapped to its label
No network call is made to populate the dropdown — values come from the in-memory constants registry

Technical Requirements

frameworks
Flutter
Riverpod
data models
ActivityType
BufdirCategory (enum from constants registry)
performance requirements
Dropdown list renders within 16ms (single frame) — list is static, no async loading
Search filter applies synchronously with no perceptible lag for lists up to 50 categories
security requirements
Enum key stored in form state must be validated against the constants registry before being passed to the bloc to prevent injection of arbitrary strings
ui components
DropdownButtonFormField or custom SearchableDropdown widget
Semantics wrapper with liveRegion for screen reader announcements
InputDecoration with helperText and errorText slots
Design token focus border and color tokens

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Source all Bufdir category values from a single constants file (e.g., `lib/constants/bufdir_categories.dart`) exporting a `List` with `{key: String, label: String}`. Never duplicate this list in the widget. Use `DropdownButtonFormField` with a custom `selectedItemBuilder` to show the label while storing the key. For accessibility, wrap the field in a `Semantics` widget with `container: true` and use `SemanticsService.announce` on value change for VoiceOver compatibility.

Apply design token color `colorSurface` for the dropdown background and `colorOnSurface` for text. In edit mode, resolve the pre-selected key via `BufdirCategories.fromKey(existingKey)` before building the widget — handle the case where a legacy key no longer exists in the registry by showing a warning label and forcing re-selection.

Testing Requirements

Write widget tests (flutter_test) covering: (1) dropdown renders all constants registry entries; (2) selecting an item stores the enum key, not the label; (3) submitting without selection shows validation error; (4) helper text is always present; (5) search filter narrows options correctly; (6) edit mode pre-populates correctly. Add a golden test for the dropdown in both empty and selected states. Verify Semantics tree contains a liveRegion node on selection change. Achieve 100% branch coverage on the validation path.

Component
Activity Type Form Screen
ui medium
Epic Risks (3)
high impact medium prob dependency

The Bufdir reporting category list is defined externally by Bufdir and may change between reporting years. If the dropdown in ActivityTypeFormScreen is hardcoded, existing activity type mappings could become invalid after a Bufdir schema update, breaking export validation for all organisations.

Mitigation & Contingency

Mitigation: Store the valid Bufdir category list in a Supabase configuration table (bufdir_categories) rather than as a Dart constant, so it can be updated by an admin without a mobile app release. Load the list in the form screen via a lightweight repository call cached locally.

Contingency: If the Bufdir category list cannot be externalised before the admin screen ships, expose a manual override field that allows coordinators to enter a raw Bufdir category code as a fallback, and schedule the configuration table migration as a follow-up task.

medium impact medium prob technical

Reusing ActivityTypeFormScreen for both creation and editing requires careful Riverpod provider scoping. If the form provider is not properly reset between navigation events, stale values from a previously edited type may pre-populate a new creation form, leading to incorrect data being saved.

Mitigation & Contingency

Mitigation: Scope the form state provider to the route using Riverpod's autoDispose modifier, ensuring the state is torn down when the screen is popped. Write a widget test that navigates to edit type A, pops, navigates to create new, and asserts all fields are empty.

Contingency: If provider scoping proves complex with the current router setup, fall back to separate widget implementations for create and edit that share a common form widget but maintain independent provider instances.

high impact low prob integration

Archiving an activity type must not break historical Bufdir export queries that filter activities by type. If the export pipeline performs an INNER JOIN against only active activity types, archived types will cause historical activities to be silently excluded from exports, producing incorrect reporting data.

Mitigation & Contingency

Mitigation: Audit all downstream query builders (Bufdir export, stats aggregation) before shipping the archive feature to confirm they join against all activity types regardless of is_active status. Add an integration test that archives a type, then asserts historical activity records for that type still appear in export queries.

Contingency: If a downstream query is discovered to filter on is_active post-launch, apply a targeted Supabase view fix that unions active and archived types for export contexts without requiring a mobile app update.