high priority low complexity integration pending integration specialist Tier 2

Acceptance Criteria

Riverpod provider `recurringTemplatesProvider` fetches from `recurring_activity_templates` Supabase table filtered by the authenticated coordinator's organisation_unit_id
RLS policy on the table is the server-side enforcement — client query must not rely solely on client-side filtering (defence in depth)
On second and subsequent widget opens, templates are served from the Riverpod cache without a new network round-trip (cache hit observable in network logs)
Cache is invalidated and re-fetched when the coordinator creates or deletes a template
Network error state is surfaced as AsyncError and rendered by the widget as a retry affordance
Empty list (zero templates) returns an empty List<RecurringTemplate> — not null, not error
Provider is scoped so that templates from one coordinator's session are not visible to another coordinator logged in on the same device after re-login
Response mapping from Supabase JSON to RecurringTemplate model handles missing optional fields without throwing

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
apis
Supabase PostgreSQL — recurring_activity_templates table
data models
activity_type
performance requirements
Initial fetch completes in under 2 seconds on a 4G connection
Cache hit response served in under 50ms
security requirements
RLS policy on recurring_activity_templates restricts rows to coordinator's organisation_unit_id using auth.uid()
Service role key never used client-side — use anon key with RLS
JWT from Supabase Auth is automatically attached by the SDK — do not manually attach tokens

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Create `RecurringTemplateRepository` with a single method `Future> fetchForCurrentCoordinator()`. Inject `SupabaseClient` via Riverpod provider. Use Riverpod's `keepAlive: true` on the `AsyncNotifierProvider` to maintain cache across widget rebuilds — but invalidate via `ref.invalidate()` on mutation events. The Supabase query: `supabase.from('recurring_activity_templates').select('id, activity_type_id, activity_types(name), default_duration_minutes, notes_template').order('created_at', ascending: false)`.

Map the joined `activity_types(name)` into `activityTypeName` on the model. Handle the case where `activity_type_id` references a deleted activity type (return the template with a fallback name 'Unknown type'). Do not use `.single()` — use `.select()` which returns a list and handles zero rows gracefully.

Testing Requirements

Integration tests using flutter_test with a mocked Supabase client (use `mocktail` to mock `SupabaseClient`). Test cases: (1) successful fetch maps JSON correctly to List, (2) empty table returns empty list, (3) network exception surfaces as AsyncError, (4) provider cache returns stale data on second call without hitting the mock (verify mock called only once). Also write a manual integration test checklist: verify RLS by logging in as coordinator A and confirming coordinator B's templates are not returned.

Epic Risks (3)
medium impact medium prob technical

The 2-hour window duplicate detection logic requires querying existing proxy records with compound key matching (mentor + date + activity type within time range). If the query is too broad it produces false positives that frustrate coordinators; if too narrow it misses genuine duplicates that corrupt Bufdir data.

Mitigation & Contingency

Mitigation: Define the duplicate detection window as a configurable parameter from the start. Prototype the Supabase query with representative data covering edge cases (midnight boundaries, different activity types same day, same activity type different mentors) before finalising the implementation.

Contingency: If the detection produces excessive false positives in UAT, allow coordinators to explicitly acknowledge and bypass the duplicate warning with a reason field, preserving safety while reducing friction.

high impact medium prob scope

If the proxy registration form does not clearly distinguish between the acting coordinator and the attributed mentor, coordinators may submit records attributing activities to themselves, causing inaccurate Bufdir reporting and potential funding issues.

Mitigation & Contingency

Mitigation: Conduct UAT with at least one real coordinator via TestFlight before release. Use distinct visual treatment (different card colours, explicit 'Registering on behalf of:' label) and require the confirmation screen to show both identities prominently.

Contingency: Add a mandatory confirmation checkbox on the confirmation screen that explicitly names the attributed mentor, preventing accidental self-attribution from slipping through.

high impact medium prob security

Coordinators with multi-chapter access must select an active chapter context before the mentor list is filtered correctly. If chapter scope resolution fails or is bypassed, cross-org proxy registrations could occur, violating data isolation between chapters.

Mitigation & Contingency

Mitigation: Reuse the existing active-chapter-state and hierarchy-service components established by the org hierarchy feature. Add a guard that blocks entry to the proxy flow if no chapter context is active, prompting chapter selection first.

Contingency: If the chapter resolution service is unavailable, default to the most restrictive scope (no mentors visible) and surface a clear error message rather than showing an unfiltered mentor list.