critical priority medium complexity infrastructure pending backend specialist Tier 2

Acceptance Criteria

organizationLabelsNotifierProvider is declared as a StateNotifierProvider<OrganizationLabelsNotifier, TerminologyState> in TerminologyRiverpodProviders
The provider uses .autoDispose so the notifier is disposed when no widget is watching it (prevents stale state after logout)
The provider reads the current authenticated orgId from an existing auth/tenant provider — it does not hardcode or derive orgId itself
If the auth state indicates no authenticated user (null orgId), the provider returns a notifier in TerminologyState.loading() and does not attempt a fetch
OrganizationLabelsNotifier is constructed with TerminologyRepository and TerminologyCacheAdapter injected via provider reads — no manual instantiation of dependencies
A derived labelProvider (Provider<String Function(String key)>) is declared alongside it, exposing the synchronous label() helper to widgets without exposing the full notifier
All providers are defined as top-level final variables (not inside a class or function) following Riverpod conventions
Provider overrides for testing are supported: TerminologyRepository and TerminologyCacheAdapter providers can be overridden in ProviderContainer during tests
Integration test: create a ProviderContainer with mock repository override, listen to organizationLabelsNotifierProvider, verify state transitions from loading to loaded after mock fetch resolves
dart analyze reports zero issues on the providers file

Technical Requirements

frameworks
Flutter
Riverpod (flutter_riverpod)
Riverpod autoDispose
apis
StateNotifierProvider.autoDispose
ref.watch (auth/tenant provider for orgId)
ref.read (repository, cache adapter providers)
data models
OrganizationId
TerminologyState
OrganizationLabelsNotifier
performance requirements
Provider creation must be synchronous — all async work happens inside the notifier, not in the provider factory
labelProvider must return a closure that performs a pure Map lookup, completing in O(1)
security requirements
The provider must not cache or retain the orgId beyond the autoDispose lifecycle — each new authenticated session creates a fresh notifier with the new orgId
Do not expose TerminologyCacheAdapter or TerminologyRepository providers publicly if they are internal to the feature layer

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Declare providers in a dedicated file (e.g., terminology_providers.dart). Follow the pattern: final _terminologyRepositoryProvider = Provider(...) as an internal provider, then organizationLabelsNotifierProvider = StateNotifierProvider.autoDispose<...>((ref) { final orgId = ref.watch(currentOrgIdProvider); final repo = ref.read(_terminologyRepositoryProvider); final cache = ref.read(_terminologyCacheAdapterProvider); return OrganizationLabelsNotifier(orgId: orgId, repository: repo, cache: cache); }). For the null orgId case, pass a sentinel value or use an Option type — do not use a nullable orgId inside the notifier; let the provider guard it. The labelProvider should be: Provider.autoDispose((ref) { final state = ref.watch(organizationLabelsNotifierProvider); return ref.read(organizationLabelsNotifierProvider.notifier).label; }) — exposing the function reference, not a computed string.

Prefer Riverpod 2.x syntax (ref.watch/ref.read) over legacy context extensions.

Testing Requirements

Integration tests using flutter_test with ProviderContainer. Override terminologyRepositoryProvider and terminologyCacheAdapterProvider with mocks. Test: (1) container with valid orgId → notifier created, fetch called, state transitions to loaded; (2) container with null orgId → notifier stays in loading, no fetch; (3) container disposed → notifier.dispose() called (verify via mock or flag); (4) labelProvider returns correct string when loaded, returns key when loading. Use ProviderContainer(overrides: [...]) pattern.

No widget tests needed for provider declarations — focus on state and lifecycle correctness.

Component
Terminology Riverpod Providers
infrastructure low
Epic Risks (3)
high impact medium prob technical

When a user switches organization context (e.g., a coordinator with multi-org access), a race condition between the outgoing organization's map disposal and the incoming organization's fetch could briefly expose the wrong organization's terminology to the widget tree.

Mitigation & Contingency

Mitigation: Implement an explicit loading state in OrganizationLabelsNotifier that widgets check before rendering any resolved labels. The provider graph should cancel the previous organization's fetch via Riverpod's ref.onDispose before initiating the next.

Contingency: If the race manifests in production, fall back to English defaults during the transition window and emit a Sentry error event for investigation; the UX impact is a brief English flash rather than wrong-org terminology.

high impact low prob security

Supabase Row Level Security policies on organization_configs may inadvertently restrict the authenticated user from reading their own organization's labels JSONB column, causing silent empty maps that appear as English fallbacks.

Mitigation & Contingency

Mitigation: Write and test explicit RLS policies that grant SELECT on the labels column to any authenticated user whose organization_id matches. Add an integration test that verifies label fetch succeeds for each role (peer mentor, coordinator, admin).

Contingency: If RLS blocks are discovered in production, temporarily escalate label fetch to a service-role edge function while the RLS policy is corrected, ensuring no labels are exposed cross-organization.

medium impact medium prob scope

A peer mentor who installs the app for the first time with no internet connection will have no cached terminology map and will see only English defaults, which may be confusing for organizations like NHF that use Norwegian-specific role names exclusively.

Mitigation & Contingency

Mitigation: Bundle a default fallback terminology map for each known organization as a compile-time asset (Dart asset file) so that even fresh installs without connectivity render correct organizational terminology immediately.

Contingency: If bundled assets are out of date, display a one-time informational banner noting that terminology will update on next connectivity restore, with no functional blocking of the app.