critical priority medium complexity frontend pending frontend specialist Tier 7

Acceptance Criteria

Screen fetches activity types scoped to the currently active orgId from the Riverpod session provider — never uses a hardcoded org
While loading, a skeleton shimmer card grid matching the expected card layout is displayed (minimum 3 skeleton cards)
When the list is empty, an empty-state illustration with the message 'No activity types yet. Create the first one.' is shown
When a network or Supabase error occurs, an error state with a 'Retry' button is shown; tapping Retry re-fetches
A 'Create activity type' button is visible in the page header at all times (not only in empty state)
Tapping 'Create activity type' navigates to ActivityTypeFormScreen in create mode
On return from ActivityTypeFormScreen with a new entity, the list refreshes to include the new item without full page reload
The screen is accessible: page title announced by screen reader, list items are focusable, skip-to-content available
Design token card-grid-2 layout is used (2-column on tablet/landscape, 1-column on phone portrait)
Coordinator and org-admin roles can access this screen; peer mentor role receives a 403/no-access redirect

Technical Requirements

frameworks
Flutter
Riverpod
BLoC (for screen-level state)
apis
ActivityTypeService.getAll(orgId)
ActivityTypeService.getAll(orgId, includeArchived: true)
data models
ActivityType
Organisation (active org context)
performance requirements
Skeleton displayed within one frame of screen mount — no blank white flash
List renders up to 100 items without jank using ListView.builder (no Column with children)
Re-fetch on return from form completes within 2 seconds on a standard 4G connection
security requirements
orgId must be read from the authenticated session — never accept orgId as a route parameter without re-validating against session
RLS enforced on Supabase side; client must not filter results to simulate access control
ui components
Skeleton shimmer widget (custom or shimmer package)
Empty state illustration widget
Error state widget with retry button
Page header with action button slot
Card grid layout using design token spacing

Execution Context

Execution Tier
Tier 7

Tier 7 - 84 tasks

Can start after Tier 6 completes

Implementation Notes

Model the screen state with a simple Cubit: `ActivityTypeListState` with `loading`, `loaded(List items, bool showArchived)`, and `error(String message)` variants. Read `activeOrgProvider` (Riverpod) in the screen's `initState` equivalent (via `ref.read` in a `ConsumerStatefulWidget`) and pass orgId to the Cubit constructor. Use `ListView.builder` with a `SliverAppBar` containing the 'Create' action for smooth scroll behavior. For the return-refresh pattern: use `await Navigator.push(...)` and, if the result is non-null, call `cubit.itemCreated(result)` to prepend the new entity to the list state — avoids a redundant network round-trip.

Apply `Semantics(header: true)` to the page title widget for VoiceOver. Skeleton cards must match the exact dimensions of the real `ActivityTypeListItem` to prevent layout shift on load completion.

Testing Requirements

Write widget tests for all three async states: loading (skeleton visible), empty (illustration + message visible), error (error widget + retry button visible). Test that tapping 'Create activity type' triggers navigation to ActivityTypeFormScreen. Test that the screen reads orgId from the Riverpod provider (not a hardcoded value) by overriding the provider in tests. Test role-based access: mock a peer-mentor session and assert no-access screen is shown.

Integration test: mount screen against Supabase local emulator, verify list populates with seeded data. Test list refresh after form return by simulating a router pop with a result entity.

Component
Activity Type Admin 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.