high priority low complexity backend pending backend specialist Tier 3

Acceptance Criteria

OrganizationLabelsProvider gains a getActivityTypeDisplayName(activityTypeId, orgId) method that returns the org-specific override if one exists, otherwise returns the canonical ActivityType.name
The provider loads activity type label overrides from a dedicated source (Supabase table, JSON config, or org settings record) — the source must be documented in code comments
For an org with no override defined for a given activity type, the canonical name is returned without errors or warnings
For NHF, HLF, and Blindeforbundet, at least one example override is configured in the data source to validate the mechanism end-to-end
The override lookup is cached per org session — repeated calls for the same orgId do not trigger additional database queries
If the override data source is unavailable (network error), the provider gracefully falls back to canonical names and logs a warning — it does not throw
The existing OrganizationLabelsProvider API is not broken — no existing method signatures change
The Riverpod provider for OrganizationLabelsProvider is updated to expose the new method without requiring consumers to reconstruct the provider
getActivityTypeDisplayName is accessible from both BLoC and UI layers via the Riverpod provider

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
apis
Supabase REST API — org_labels or activity_type_labels table (SELECT scoped to orgId)
data models
ActivityType
OrganizationLabel (new or existing)
performance requirements
Override map loaded once per org session and cached in memory — no per-activity-type round trips
Cache must be invalidated when org context changes (org switch or logout)
security requirements
Label overrides are org-scoped — org A cannot read or influence org B's label configuration
Supabase query for overrides must include .eq('org_id', orgId) filter

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Read the existing 075-organization-labels-provider implementation fully before modifying it — the extension must match the existing patterns (naming, error handling, caching strategy). The cleanest approach is to add an `activityTypeLabels` map to the existing labels data structure loaded from Supabase, keyed by activity_type_id. If the org_labels Supabase table does not yet have an activity_type_labels column or table, add a TODO for the database migration and use an empty map as the default. The fallback chain is: (1) org-specific override from Supabase → (2) ActivityType.name from the domain model.

Do not introduce a third fallback layer. For caching, use a private Map> (_labelsByOrgId) where the outer key is orgId and the inner key is activityTypeId. This integrates with the existing labels caching without duplication. Consider using an AsyncNotifierProvider to handle the async load cleanly, but match the existing provider type in the codebase for consistency.

Testing Requirements

Unit tests with flutter_test and MockSupabaseClient: (1) getActivityTypeDisplayName returns org-specific override when one exists in the mock data; (2) getActivityTypeDisplayName returns canonical ActivityType.name when no override exists; (3) getActivityTypeDisplayName returns canonical name when the overrides data source returns an empty list; (4) network error during override load — assert canonical name is returned and no exception propagates; (5) cache hit — second call for same orgId does not trigger a second Supabase query (verify mock is called only once); (6) cache invalidation — after org switch, stale overrides are not returned. Widget test: render an activity type name in a widget using the provider and assert the overridden label appears for HLF's org context.

Component
Organization Labels Provider
infrastructure low
Epic Risks (3)
high impact medium prob technical

The JSONB metadata column has no enforced schema at the database level. If the Dart model and the stored JSON diverge (e.g., a field is renamed or a new required flag is added without a migration), the metadata resolver will silently return null or throw at parse time, breaking conditional wizard logic for all organisations.

Mitigation & Contingency

Mitigation: Define a versioned Dart Freezed model for ActivityTypeMetadata and add a Supabase check constraint or trigger that validates the JSONB structure on write. Document the canonical metadata schema in a shared constants file and require schema review for any metadata field additions.

Contingency: Implement a lenient parse path in ActivityTypeMetadataResolver that returns safe defaults for missing fields and logs a structured warning to Supabase edge logs, allowing the app to degrade gracefully rather than crash.

high impact low prob security

If RLS policies on the activity_types table are misconfigured, a coordinator from one organisation could read or mutate activity types belonging to another organisation, violating data isolation guarantees required by all three client organisations.

Mitigation & Contingency

Mitigation: Write integration tests against the Supabase local emulator that explicitly assert cross-org isolation: a token scoped to org A must receive zero rows when querying org B activity types, and upsert attempts must return permission-denied errors.

Contingency: Apply an emergency RLS policy patch via Supabase dashboard without a code deploy. Audit all activity_type rows for cross-org contamination and restore from backup if any data leakage is confirmed.

medium impact medium prob integration

If the cache invalidation call in ActivityTypeService is not reliably triggered after an admin creates, edits, or archives an activity type, peer mentors on the same device will see stale data in the registration wizard until the next app restart, leading to confusion and potential misregistrations.

Mitigation & Contingency

Mitigation: Enforce a strict pattern: ActivityTypeService always calls cacheProvider.invalidate() inside the same try block as the successful Supabase mutation, before returning to the caller. Write a widget test that verifies the cache notifier emits an updated list after a service mutation.

Contingency: Add a background Supabase Realtime subscription on the activity_types table that triggers cache invalidation automatically, providing an independent safety net independent of the service call path.