critical priority medium complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

ActivityTypeSelectionScreen is a ConsumerWidget (or ConsumerStatefulWidget) that watches the activityTypeListProvider(orgId) Riverpod provider
Loading state: displays a centered CircularProgressIndicator while AsyncValue is in loading state
Empty state: displays a descriptive empty-state message ('No activity types available') with a retry button when list is empty
Error state: displays a user-friendly error message and a retry button when AsyncValue is in error state; error details are not shown to end users
List state: renders a ListView.builder with one ListTile per ActivityType, showing resolvedLabel as title and a trailing chevron icon
Tapping a ListTile calls Navigator.pop(context, selectedActivityType) passing the full ActivityType object back to the wizard orchestrator
Screen uses the project's design token system for colors, typography, and spacing — no hardcoded color values
Screen header/title is resolved from org terminology (e.g., 'Select Activity Type' uses org label if available)
Screen passes WCAG 2.2 AA contrast requirements: list tile text has minimum 4.5:1 contrast ratio against background
ListTile tap target meets minimum 48x48dp accessibility touch target size
Screen announces list item labels correctly to VoiceOver/TalkBack screen readers (Semantics widget or default ListTile semantics)
Widget test: loading state renders CircularProgressIndicator
Widget test: list with 3 types renders 3 ListTile widgets with correct labels
Widget test: tapping first ListTile pops route with correct ActivityType object
Widget test: error state renders error message and retry button

Technical Requirements

frameworks
Flutter
Riverpod
Dart
data models
activity_type
performance requirements
ListView must use ListView.builder (lazy rendering) not ListView with children list — required even for small lists for consistency
Screen must render first frame within 16ms (60fps) — no blocking synchronous operations in build()
Activity type list is served from cache (task-003) — no loading spinner on repeat navigation to this screen within TTL
security requirements
Screen must not display internal IDs, labelKeys, or system metadata to end users — only resolvedLabel
ActivityType metadata fields (triggersReimbursementWorkflow, isTravelExpenseEligible) must not be exposed in the list UI — passed through silently for downstream use
Retry button must not expose error stack traces or Supabase error codes in UI
ui components
ActivityTypeSelectionScreen (ConsumerWidget)
ListView.builder with ListTile items
Loading state: CircularProgressIndicator
Empty state: EmptyStateWidget (reuse project shared widget if available)
Error state: ErrorStateWidget with retry button (reuse project shared widget if available)
Page header widget (reuse AppPageHeader from shared components)

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Place in lib/features/activity_type/presentation/screens/activity_type_selection_screen.dart. Use ref.watch(activityTypeListProvider(orgId)) and handle AsyncValue with .when(data:, loading:, error:). The orgId comes from an auth provider (e.g., ref.watch(currentOrgIdProvider)) — do not accept orgId as a constructor parameter to avoid stale values. Return the selected ActivityType via Navigator.pop(context, selected) so the calling wizard step can await Navigator.push() and receive the selection.

Ensure the ListTile trailing chevron uses Icon(Icons.chevron_right) styled with the app's secondary text color design token. For accessibility: ListTile already provides correct semantics by default; ensure the resolvedLabel is used as the tile title (not labelKey). Add a Semantics wrapper with label: 'Select ${type.resolvedLabel} as activity type' on each tile if the default ListTile semantics are insufficient for screen readers. This screen is a navigator push from the activity registration wizard — it is not a bottom nav tab, so include a back button in the app bar.

Testing Requirements

Widget tests using flutter_test with ProviderScope and overridden activityTypeListProvider: (1) loading state: override provider with AsyncLoading() → assert CircularProgressIndicator present, (2) error state: override with AsyncError() → assert error message visible and retry button tappable, (3) empty state: override with AsyncData([]) → assert empty message visible, (4) populated list: override with AsyncData([type1, type2, type3]) → assert 3 ListTiles with correct labels, (5) tap test: tap first ListTile → verify Navigator.pop was called with type1 as argument using NavigatorObserver mock, (6) accessibility test: run flutter_test SemanticsController to verify list items have meaningful semantics labels, (7) golden test (optional): snapshot the populated list state for visual regression detection. All tests must pass with flutter_test — no integration_test needed for this screen.

Epic Risks (2)
medium impact medium prob scope

Metadata flag combination rules differ between organisations (e.g., Blindeforbundet honorarium thresholds, HLF mutual exclusion of km and transit). Encoding these as generic service-level validation may be insufficient, forcing organisation-specific branching inside the service that becomes unmaintainable as new organisations are onboarded.

Mitigation & Contingency

Mitigation: Model flag validation as a pure function that accepts an ActivityTypeMetadata object and an org configuration record, making org-specific rules data-driven rather than hardcoded. Establish the validation contract in the foundation epic so the service just delegates to the validator.

Contingency: Defer complex cross-flag validation to a lightweight edge function that can be updated without a mobile app release, accepting that initial validation in the mobile service layer is permissive and corrected server-side.

high impact low prob technical

Blindeforbundet users rely on VoiceOver and JAWS. If the selection screen is built with non-semantic widgets that fail accessibility audit late in the sprint, a significant rework of the widget tree may be required, blocking the registration wizard integration.

Mitigation & Contingency

Mitigation: Build the selection screen against the project's established accessibility design tokens and semantics wrapper conventions from the start. Run Flutter's semantic tree inspector and a manual VoiceOver pass before marking any widget task complete.

Contingency: Wrap all tappable items in the project's SemanticsWrapperWidget and schedule a dedicated accessibility review session with a screen reader user from Blindeforbundet before the epic is closed.