critical priority low complexity integration pending frontend specialist Tier 6

Acceptance Criteria

OrgSelectionScreen calls OrgSelectionService.getAvailableOrganizations() exactly once on first mount via Riverpod provider
While the fetch is in-flight, a centered CircularProgressIndicator is displayed in the list area; the CTA button remains disabled
On successful fetch, the returned organizations are rendered as OrgCard widgets in the ListView in the order returned by the service
On fetch failure (network error, Supabase error, timeout), the loading indicator is replaced by a plain-language error message in Norwegian (e.g. 'Kunne ikke hente organisasjoner. PrΓΈv igjen.') with a retry button
Tapping the retry button re-invokes the fetch and shows the loading indicator again
An empty list from the service renders an empty state message rather than an invisible blank screen
The service call is made through a Riverpod FutureProvider or AsyncNotifier β€” not directly in initState
Screen state correctly rebuilds when the provider emits loading β†’ data or loading β†’ error transitions
No organization data is duplicated in local widget state β€” the Riverpod provider is the single source of truth for the list

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase PostgreSQL 15 (via OrgSelectionService)
performance requirements
Fetch must complete within 3 seconds on a standard 4G connection; show error state if exceeded
Provider must be auto-disposed when screen is popped to prevent memory leaks
security requirements
Organization list fetched via Supabase RLS-protected query β€” client never bypasses row-level security
No raw Supabase credentials used in widget layer β€” all access via service abstraction
ui components
CircularProgressIndicator (centered in list area)
Error state widget with plain-language message and retry AppButton
Empty state widget with guidance text
OrgCard (002-org-card) for each returned organization

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Use `AsyncValue` from Riverpod to handle loading/data/error states declaratively. The provider should be defined as `final orgListProvider = FutureProvider.autoDispose>((ref) => ref.read(orgSelectionServiceProvider).getAvailableOrganizations())`. In the widget, use `ref.watch(orgListProvider).when(data: ..., loading: ..., error: ...)`. Do not use `ref.read` in build β€” always `ref.watch` for reactive rebuilds.

For the error state, avoid technical error strings (no 'SocketException' shown to user). The retry pattern is: `ref.invalidate(orgListProvider)` which forces a re-fetch. Ensure the error message meets cognitive accessibility requirements from the NHF workshop β€” short sentences, plain language, clear next action.

Testing Requirements

Unit test OrgSelectionService mock using Mockito or manual stub. Widget tests: Test 1: provide a mock service that returns a delayed future, assert CircularProgressIndicator is shown during delay. Test 2: provide mock service returning 3 orgs, assert 3 OrgCard widgets rendered after future resolves. Test 3: provide mock service that throws an exception, assert error message widget appears with retry button.

Test 4: tap retry button after error, assert service.getAvailableOrganizations() called a second time. Test 5: provide mock service returning empty list, assert empty state widget visible (no OrgCards, no progress indicator). Integration test: use ProviderContainer with real Riverpod providers and stubbed Supabase client.

Epic Risks (3)
high impact medium prob technical

Flutter's Semantics system requires explicit configuration for custom card widgets β€” the default widget tree semantics may merge child nodes incorrectly, causing VoiceOver to announce partial or garbled text for each OrgCard. This is a critical failure for Blindeforbundet and violates the contractual WCAG 2.2 AA requirement.

Mitigation & Contingency

Mitigation: Wrap each OrgCard in a Semantics widget with an explicit label combining organization name and role label, set button role, and excludeSemantics: true on all child widgets to prevent double-announcement. Test on a physical iOS device with VoiceOver enabled before marking the epic complete.

Contingency: If automated semantics are insufficient, implement a dedicated AccessibilityLabel builder in the OrgCard that constructs the announcement string from OrgLabelsProvider output and passes it directly to the Semantics label field.

medium impact medium prob technical

Organization logos must load before the screen is considered ready. If assets are fetched from a remote URL and network latency is high (a realistic scenario for rural NHF users), the screen may render with broken image placeholders, degrading the first-impression quality and potentially confusing screen reader users whose VoiceOver announcements would reference an empty image.

Mitigation & Contingency

Mitigation: Cache organization logos locally after first load using the org-branding-cache pattern. For the initial load, render a styled placeholder with the organization's initials and primary color while the logo loads asynchronously. Ensure the Semantics label is derived from the organization name, not the image asset, so screen reader announcements are never dependent on image load state.

Contingency: If caching implementation is out of scope for this epic, ship with initials-based placeholders as permanent fallbacks and defer logo loading to the org-branding-cache epic. The screen remains fully functional and accessible without logos.

medium impact low prob scope

The user story specifies that single-organization users bypass the selection screen and are routed directly into the app. If the bypass logic is implemented in the UI layer rather than in OrgSelectionService, it creates a tight coupling that breaks if the routing logic is ever reused or tested in isolation. It also creates a potential timing issue where the screen briefly renders before the bypass fires.

Mitigation & Contingency

Mitigation: Implement the single-org bypass entirely in OrgSelectionService.getPersistedSelection() or as a route guard β€” the screen should never be pushed onto the navigation stack for single-org users. The OrgSelectionScreen widget itself has no knowledge of this bypass.

Contingency: If the route guard approach is deferred to the routing epic, add a local guard in the OrgSelectionScreen's initState that calls OrgSelectionService, auto-selects if count is 1, and navigates forward before the first frame is painted, using a loading indicator to prevent the flash.