high priority low complexity backend pending backend specialist Tier 3

Acceptance Criteria

A referralCodeRepositoryProvider (Provider<ReferralCodeRepository>) is defined and reads from the existing supabaseClientProvider
A recruitmentAttributionRepositoryProvider (Provider<RecruitmentAttributionRepository>) is defined and reads from supabaseClientProvider
A shareSheetBridgeProvider (Provider<ShareSheetBridge>) is defined as a global singleton
A qrCodeGeneratorProvider (Provider<QrCodeGenerator>) is defined as a global singleton
A deepLinkHandlerProvider (Provider<DeepLinkHandler>) is defined as a global singleton
Organisation-scoped query providers (e.g. referralCodesForOrgProvider) use family modifier keyed on organisationId so different orgs do not share cached state
All five providers are exported from a single barrel file at lib/features/recruitment/infrastructure/recruitment_providers.dart
No provider directly instantiates SupabaseClient — all use the existing supabaseClientProvider to maintain single source of truth
Provider graph has no circular dependencies (verified by running the app without errors)
Existing providers in the codebase are not modified — new providers are additive only

Technical Requirements

frameworks
Riverpod (flutter_riverpod)
Riverpod code generation (riverpod_generator) if already used in the project — follow existing pattern
apis
Supabase Dart client (existing supabaseClientProvider)
Organisation context provider (existing)
data models
ReferralCode
RecruitmentAttributionEvent
Organisation (scoping key)
performance requirements
Repository providers must be Provider (not FutureProvider/StreamProvider) — they are synchronous factories; async work happens inside repository methods
Organisation-scoped family providers must dispose cached state when the organisation scope is left to prevent memory leaks
security requirements
Providers must not cache sensitive referral tokens in global state — tokens must only live in scoped, short-lived state
Organisation scoping must be enforced at the provider level so coordinators cannot accidentally read another org's referral data

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Follow the existing provider file conventions in the project — if riverpod_generator annotations (@riverpod) are used elsewhere, adopt the same pattern for consistency. Place all five provider definitions in a single file (recruitment_providers.dart) to make the dependency graph easy to review. For the organisation-scoped family providers, use Provider.family keyed on organisationId (String) rather than the full Organisation object to avoid hashCode issues with complex types. The barrel file (recruitment_providers.dart at the feature root or an index.dart) should re-export only the provider symbols — not the concrete repository/utility classes — so consumers depend on the abstraction.

Add a brief inline comment on each provider explaining its scope (singleton vs. org-scoped) to aid future maintainers.

Testing Requirements

Write unit tests using ProviderContainer (from riverpod) to verify provider wiring without a full Flutter widget tree. Test cases: (1) referralCodeRepositoryProvider resolves to a ReferralCodeRepository instance given a mock supabaseClientProvider override, (2) recruitmentAttributionRepositoryProvider resolves correctly, (3) shareSheetBridgeProvider, qrCodeGeneratorProvider, deepLinkHandlerProvider each resolve as singletons (same instance on repeated reads), (4) family-keyed organisation provider returns distinct instances for different organisationId values, (5) barrel file exports all five providers without name conflicts. Use flutter_test with ProviderContainer and mockito/mocktail for dependency overrides. Target 100% provider resolution coverage (all providers are exercised at least once).

Component
Referral Code Repository
data low
Dependencies (5)
Implement the QrCodeGenerator Flutter widget wrapping the qr_flutter package. The widget accepts a data string (referral URL), size (double), errorCorrectionLevel (QrErrorCorrectLevel), foregroundColor, and backgroundColor (for organisation branding). Expose a static helper generateBytes(data, size) → Uint8List for use by the share sheet. Ensure the widget degrades gracefully when the data string is empty and renders accessibly with a semantic label describing the QR code purpose. epic-membership-recruitment-foundation-task-005 Implement the ShareSheetBridge Dart service wrapping the share_plus package. Expose shareReferralCode(referralUrl, mentorName, orgName) → Future<void> which composes a default Norwegian-language message ('Hei! Jeg er likeperson i [orgName]. Bli med via denne lenken: [referralUrl]') and invokes the native share sheet. Optionally accept a qrImageBytes parameter to attach the QR image to the share payload. Handle ShareResultStatus and surface errors to the caller. epic-membership-recruitment-foundation-task-006 Implement the ReferralCodeRepository Dart class backed by the Supabase client. Expose methods: createCode(mentorId, orgId) → ReferralCode, deactivateCode(codeId), getActiveCodeForMentor(mentorId, orgId) → ReferralCode?, lookupByCodeString(codeString) → ReferralCode?, and listCodesForOrg(orgId) → List<ReferralCode>. Enforce the one-active-code-per-mentor-per-organisation invariant at the repository level by deactivating any existing active code before inserting a new one. Handle Supabase exceptions and map them to domain exceptions. epic-membership-recruitment-foundation-task-003 Implement the RecruitmentAttributionRepository Dart class backed by Supabase. Expose methods: recordEvent(referralCodeId, eventType, metadata) → void, getEventsForCode(referralCodeId) → List<ReferralEvent>, getConversionFunnelForOrg(orgId, dateRange) → AttributionFunnel, and getTotalConversionsByMentor(orgId) → List<MentorConversionStats>. The aggregation queries must be efficient using Supabase RPC or views defined in the migration. All reads enforce RLS so coordinators only see their organisation's data. epic-membership-recruitment-foundation-task-004 Implement the DeepLinkHandler service that intercepts incoming deep links matching the referral URI scheme (e.g., app://referral?token=XYZ or https://app.example.com/referral/XYZ). Integrate with the app's existing GoRouter configuration to route matched URIs to the new-member onboarding screen, passing the extracted token as a route parameter. Handle both cold-start (app launched from link) and foreground-resume (app already running) cases using the app_links package. Ensure unrecognised links are passed through to existing handlers without interference. epic-membership-recruitment-foundation-task-007
Epic Risks (3)
high impact medium prob technical

iOS Universal Links and Android App Links have distinct configuration requirements (apple-app-site-association, assetlinks.json, entitlements). A misconfiguration causes the OS to open the referral URL in a browser instead of the app, completely breaking the onboarding funnel for new members on one platform.

Mitigation & Contingency

Mitigation: Configure both Universal Links and App Links from the start of this epic using the project's existing Supabase-hosted domain. Write an E2E test on both simulators that taps a referral URL and asserts the onboarding screen is reached. Document the required server-side JSON files alongside the migration.

Contingency: If platform deep-link configuration cannot be resolved before the UI epics need the handler, implement a fallback custom-scheme URI (e.g., likeperson://referral?code=XYZ) that works unconditionally, and schedule Universal/App Link fix as a follow-up task.

high impact high prob security

Referral click events must be writable without an authenticated session (a new member who has not yet registered is tapping the link). Standard Supabase RLS cannot grant anonymous inserts without opening a security hole. If this is not solved early it blocks the entire attribution pipeline.

Mitigation & Contingency

Mitigation: Design referral_events writes to go exclusively through a Supabase Edge Function that validates the referral code exists and is active before inserting. The Edge Function uses the service-role key server-side; the client only calls the function endpoint. This is documented in the feature spec.

Contingency: If the Edge Function approach is delayed, temporarily allow anon inserts restricted by a CHECK constraint that event_type = 'click' and new_member_id IS NULL, then tighten to Edge Function writes in a follow-up migration before the feature goes to production.

medium impact low prob dependency

The qr_flutter package version pinned in pubspec may conflict with the current Flutter SDK version or with other packages in the monorepo, causing build failures that block QR code delivery.

Mitigation & Contingency

Mitigation: Verify qr_flutter compatibility against the project's Flutter SDK version as the very first task in this epic. If a conflict exists, resolve it before any other work proceeds.

Contingency: If qr_flutter cannot be made compatible, evaluate mobile_scanner (already likely in pubspec for QR scanning) which also supports generation, or implement QR generation via a lightweight Dart port as a last resort.