critical priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

fetchSummary(mentorId, year) queries the 'annual_summaries' Supabase table with eq filters on peer_mentor_id and year, and returns an AnnualSummary domain model on success
fetchSummary returns null (not an exception) when the row does not exist (empty response)
saveSummary(summary) performs an upsert on the annual_summaries table using the domain model's id as the conflict key
deleteSummary(mentorId, year) issues a delete with matching eq filters and completes silently when no row exists
All three methods retry up to 3 times with exponential backoff (1 s, 2 s, 4 s) on PostgrestException with a 5xx status code or socket/timeout errors
Non-retryable errors (4xx, auth errors) are wrapped in a typed AnnualSummaryRepositoryException and rethrown immediately without retrying
Data mapper AnnualSummaryMapper.fromMap(Map<String, dynamic>) and toMap() are implemented in a separate file and handle all nullable fields and date parsing correctly
The Riverpod provider annualSummaryRepositoryProvider is a Provider<AnnualSummaryRepository> (not autoDispose) since the repository is stateless
Repository does not hold state — it is a pure data-access class
RLS on the Supabase table ensures a peer mentor can only fetch their own summaries; the repository does not add client-side org or role filtering

Technical Requirements

frameworks
Flutter
Riverpod
supabase_flutter
apis
Supabase PostgreSQL REST API — annual_summaries table
data models
annual_summary
performance requirements
fetchSummary completes within 2 s on a stable connection
Repository methods must not block the UI thread — all Supabase calls are async/await
security requirements
Supabase client uses the anon key — RLS on annual_summaries table enforces row ownership
Service role key must never be used in the Flutter client
mentorId passed to queries must originate from the authenticated JWT claims, not from user-supplied input, to prevent IDOR

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Implement the retry loop as a standalone retryWithBackoff(Future Function() fn, {int maxAttempts = 3}) utility — do not inline the loop in each method. Use supabase_flutter's .from('annual_summaries') query builder. Map Supabase timestamps (ISO 8601 strings) to DateTime using DateTime.parse() with a null-safe wrapper. Keep AnnualSummaryMapper as a static utility class; do not put mapping logic inside the repository.

The Riverpod provider should accept the SupabaseClient via ref.read(supabaseClientProvider) — do not instantiate Supabase.instance.client directly inside the repo for testability.

Testing Requirements

Unit tests with a mocked SupabaseClient (using Mockito or manual mock): (1) fetchSummary returns mapped domain model on 200 response, (2) fetchSummary returns null on empty array response, (3) saveSummary calls upsert with correct payload, (4) deleteSummary calls delete with correct filters, (5) retry logic fires up to 3 times on 503 then rethrows, (6) 401 error rethrows immediately without retry, (7) mapper handles null optional fields without throwing. Integration test against a Supabase local dev instance (optional but recommended): round-trip save and fetch.

Component
Annual Summary Repository
data medium
Epic Risks (3)
medium impact medium prob dependency

Rive animation files may not be available at implementation time, blocking the wrapped-animation-controller from being fully tested. If asset delivery is delayed, the controller cannot be validated for memory-leak-free disposal.

Mitigation & Contingency

Mitigation: Implement the animation controller with stub/placeholder AnimationController instances first so the lifecycle and disposal logic can be unit-tested independently of Rive assets. Define a named animation registry interface early so UI components can reference animations by name without coupling to specific Rive files.

Contingency: If Rive assets are not delivered before Epic 3 begins, replace Rive animations with Flutter implicit animations (AnimatedOpacity, ScaleTransition) as a drop-in and schedule Rive integration as a follow-on task once assets arrive.

high impact medium prob technical

The annual_summaries Supabase RPC aggregating 12 months of activity records per mentor may exceed acceptable query latency (>2s) for mentors with high activity volumes such as the HLF mentor with 380 registrations cited in workshop notes.

Mitigation & Contingency

Mitigation: Design the RPC to materialise summary results into the annual_summaries table via a scheduled edge function rather than computing on demand. The repository reads pre-computed rows, keeping query latency constant regardless of activity volume.

Contingency: If on-demand queries are required for real-time period switching, add a PostgreSQL partial index on (mentor_id, activity_date) and implement a client-side loading skeleton so slow queries degrade gracefully rather than blocking the UI.

medium impact low prob technical

iOS requires photo library permission before saving a screenshot to the gallery. If the permission prompt is triggered at an unexpected point in the share flow, the UX breaks and users may deny permission permanently, making gallery save unavailable.

Mitigation & Contingency

Mitigation: Trigger the permission request only when the user explicitly chooses 'Save to gallery' in the share overlay, not on screen load. Implement a pre-prompt explanation screen following Apple HIG so users understand why the permission is needed before the system dialog appears.

Contingency: If permission is denied, gracefully fall back to clipboard copy and system share sheet options which do not require photo library access, and surface a non-blocking snackbar explaining the limitation.