critical priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

selectOrganization(orgId) persists the organization ID to local storage synchronously before returning
getSelectedOrganization() returns the previously persisted Organization model after a cold app restart
getSelectedOrganization() returns null when no selection has been persisted (first launch)
getSelectedOrganization() returns null and clears stale storage when the persisted org ID no longer appears in loadOrganizations() (e.g., membership revoked)
Calling selectOrganization() a second time overwrites the previous selection — no stale data remains
selectOrganization() throws an ArgumentError if the provided orgId is empty or null
Persistence key is namespaced to avoid collisions (e.g., 'eircodex.selected_org_id')
Unit tests verify persistence survives simulated app restarts via shared_preferences mock

Technical Requirements

frameworks
Flutter
Riverpod
shared_preferences (via OrgPersistenceRepository)
data models
Organization
OrgPersistenceRepository interface
performance requirements
getSelectedOrganization() must complete synchronously or within one async tick — no blocking UI
Local storage read must not block the main isolate
security requirements
Only the organization ID (not sensitive org data) is persisted in local storage
On user logout, selectOrganization(null) or a dedicated clearSelection() must wipe the persisted value
Do not store auth tokens or PII alongside the org ID

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

OrgPersistenceRepository should wrap shared_preferences behind an interface so the service stays testable without platform channels. The key design decision: getSelectedOrganization() should not silently return a stale org that the user no longer belongs to. Implement a validation step: after reading the stored ID, cross-reference against loadOrganizations() and clear if invalid. This prevents a subtle bug where a user's membership is revoked but the app still shows the old org context.

Keep the local storage schema simple — store only the org ID string; hydrate the full Organization model from the in-memory organizations list loaded in task-003.

Testing Requirements

Use flutter_test with the shared_preferences mock (SharedPreferences.setMockInitialValues). Test cases: (1) persist and retrieve the same org ID across simulated restarts, (2) return null on first launch with no prefs set, (3) return null and clear storage when persisted org is no longer in the user's membership list, (4) overwrite existing selection with a new org ID, (5) validate that empty orgId throws. Achieve 100% branch coverage on both methods.

Component
Organization Selection Service
service low
Epic Risks (2)
high impact medium prob technical

The OrgLabelsProvider must be fully initialized before any UI widget renders organization-specific text. If the service triggers label initialization asynchronously and the UI builds before the labels stream emits, widgets will briefly display raw label keys instead of human-readable text, which constitutes a WCAG 2.2 AA failure for screen readers that announce whatever text is present at render time.

Mitigation & Contingency

Mitigation: Design the OrgSelectionService.select() method to await OrgLabelsProvider initialization before completing its Future — callers receive a resolved future only after labels are ready. Use an AsyncValue loading state to gate UI rendering via Riverpod's when() pattern.

Contingency: If awaiting initialization causes perceivable UI lag, implement an optimistic render with a skeleton loading state that is announced by the live-region-announcer as 'Loading organization content' so screen readers do not announce raw keys.

medium impact medium prob integration

If the label definitions loaded from the backend for each organization do not match the label key registry used in the UI, widgets will fall back to raw keys silently. This is particularly harmful for Blindeforbundet users relying on VoiceOver, where a raw key announced by a screen reader is incomprehensible.

Mitigation & Contingency

Mitigation: Define a compile-time label key registry (enum or const strings) and assert at OrgLabelsProvider initialization that all required keys are present in the loaded map. Log a structured warning for any missing key and substitute a human-readable fallback string rather than the raw key.

Contingency: If a label schema mismatch reaches production, the OrgLabelsProvider fallback mechanism ensures users see a reasonable English default rather than a raw key. A backend label patch can be deployed without an app release.