high priority medium complexity backend pending backend specialist Tier 5

Acceptance Criteria

After ActivityTypeRepository.create() completes successfully, the ActivityTypeCacheProvider re-fetches and the new type appears in the cached list within one render cycle
After ActivityTypeRepository.update() completes successfully, the updated type is reflected in the cache without requiring app restart
After ActivityTypeRepository.softDelete() completes successfully, the deleted type is removed from the cached list (is_active == false types are excluded)
Cache invalidation is triggered only on successful write operations, not on failures; a failed create/update/delete does not wipe the existing valid cache
Admin screens observing the provider (e.g., activity type list screen) rebuild automatically via Riverpod's ref.watch after invalidation
Invalidation does not cause a race condition: if two write operations complete concurrently, the final cache state reflects the last committed database state
The invalidation mechanism works correctly when the admin screen is not the active screen (background invalidation)
No duplicate network calls are made during invalidation: ref.invalidate() triggers exactly one re-fetch

Technical Requirements

frameworks
Riverpod (ref.invalidate / ref.refresh API)
Flutter
apis
ActivityTypeRepository.create(ActivityType)
ActivityTypeRepository.update(ActivityType)
ActivityTypeRepository.softDelete(String id)
Riverpod ref.invalidate(activityTypeCacheProvider)
data models
activity_type (id, organization_id, name, is_active)
performance requirements
Re-fetch after invalidation must complete and update UI within 3 seconds on 4G
Invalidation must not block the write operation's caller — fire-and-forget pattern after successful write confirmation
security requirements
Write operations that trigger invalidation must themselves be authenticated and org-scoped via repository layer
Invalidation must only affect the cache for the current organisation; other org caches (if multi-org is supported) must not be touched

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

The cleanest pattern is to call ref.invalidate(activityTypeCacheProvider) from within the write-side notifier (or use-case layer) after a confirmed successful write, rather than inside the repository itself — this keeps the repository free of Riverpod dependencies. If you use a separate ActivityTypeAdminNotifier for write operations, inject the ProviderRef and call invalidate there. Avoid ref.refresh() in favour of ref.invalidate() to let consumers control when they rebuild. Do not use Supabase Realtime for invalidation here — that introduces websocket complexity not justified for admin-only writes; direct Riverpod invalidation after confirmed mutation is sufficient and simpler.

Testing Requirements

Unit tests using flutter_test and mocktail with ProviderContainer. Test cases: (1) after repository.create() resolves, ref.invalidate is called exactly once on activityTypeCacheProvider; (2) after ref.invalidate, the provider transitions back to AsyncLoading then AsyncData with refreshed list; (3) a failed repository.create() (throws exception) does NOT call ref.invalidate; (4) after repository.softDelete(), the soft-deleted item is absent from the refreshed cache list; (5) concurrent create + update both complete without leaving the cache in a stale partial state. Use fake async to control timing in concurrency tests.

Component
Activity Type Cache Provider
data medium
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.