critical priority low complexity api pending api specialist Tier 1

Acceptance Criteria

SupabaseActivityClient has a constructor accepting a SupabaseClient instance (injected, not created internally)
insertActivity(ActivityRecord record) calls supabase.from('activities').insert() with the record's toJson() payload, strips the local_ id prefix before insert (server generates the real UUID), and returns the inserted row deserialized as an ActivityRecord with syncStatus == SyncStatus.synced
fetchActivities({required String peerId}) calls supabase.from('activities').select().eq('peer_mentor_id', peerId) and returns a List<ActivityRecord> deserialized from the response
deleteActivity(String id) calls supabase.from('activities').delete().eq('id', id) and returns Future<void>
All three methods throw a typed ActivityNetworkException (not raw PostgrestException) on Supabase errors, with the original error message preserved for logging
No dynamic or Object types appear in any public method signature — all inputs and outputs are fully typed Dart types
The client does NOT strip or modify the orgId field — it is always included in insert payloads for Row-Level Security enforcement
RLS compliance: the client relies on the authenticated Supabase session JWT for RLS enforcement — no manual org filtering in queries
Located at lib/features/activity_registration/data/clients/supabase_activity_client.dart

Technical Requirements

frameworks
Flutter
supabase_flutter
apis
Supabase PostgreSQL 15
Supabase Auth
data models
activity
activity_type
performance requirements
fetchActivities must request only needed columns via .select('id,activity_type_id,date,duration_minutes,notes,peer_mentor_id,org_id,created_at,sync_status') to minimize payload size
insertActivity must use .select() after insert to return the server-assigned row in a single round-trip
security requirements
Row-Level Security is enforced server-side on the 'activities' table — the client must NEVER add manual .eq('org_id', orgId) filters as a substitute for RLS; RLS is the authoritative security boundary
The service role key must never be used in the Flutter client — only the anon key with authenticated session JWTs
Do not log ActivityRecord fields containing notes or peerId at DEBUG or INFO level — only log error codes on failure
orgId must always be present in insert payload so RLS insert policies can validate org membership

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use supabase.from('activities').insert(payload).select().single() for insertActivity to get the server row back in one call. Before insert, replace any 'local_' prefixed id with null (or omit the id field entirely) so PostgreSQL generates the real UUID server-side. After a successful insert, set syncStatus to SyncStatus.synced on the returned record. Wrap the entire PostgREST chain in a try/catch for PostgrestException and rethrow as ActivityNetworkException(message: e.message, code: e.code).

Define ActivityNetworkException as a simple domain exception class in lib/core/exceptions/. Avoid chaining too many PostgREST modifiers in a single expression — break into local variables for readability and easier debugging.

Testing Requirements

Unit tests with a mocked SupabaseClient using mocktail. Test insertActivity: stub the PostgREST chain to return a JSON map, assert the returned ActivityRecord has syncStatus == synced and the server-assigned id (no local_ prefix). Test fetchActivities: stub select response with a list of JSON maps, assert correct deserialization into List. Test deleteActivity: verify delete().eq() was called with correct id.

Test error handling: stub any method to throw PostgrestException, assert ActivityNetworkException is thrown. Integration tests against a local Supabase instance (optional, CI-gated): verify actual insert/fetch/delete round-trips with RLS enabled.

Component
Supabase Activity Client
infrastructure low
Epic Risks (3)
high impact medium prob technical

The optimistic insert pattern requires reconciling temporary local IDs with server-assigned IDs after the async Supabase write completes. If reconciliation logic is incorrect, the UI may display stale records, duplicate entries may appear, or subsequent operations (edit, delete) may target the wrong record ID, corrupting data integrity.

Mitigation & Contingency

Mitigation: Define a clear contract for temporary ID generation (e.g., UUID prefixed with 'local-') and implement a dedicated reconciliation method in ActivityRepository that atomically swaps the temporary ID. Write integration tests that simulate the full optimistic → confirm cycle.

Contingency: If reconciliation proves too complex, fall back to a simpler non-optimistic insert with a loading spinner for the network round-trip. The UX degrades slightly but correctness is preserved. Re-introduce optimistic behaviour once the pattern is stable.

high impact medium prob integration

Supabase row-level security policies on the activities table may not be configured to match the access patterns required by the client. If RLS blocks inserts or selects for the authenticated peer mentor session, all activity registration operations will silently fail or return empty results, which is difficult to diagnose in production.

Mitigation & Contingency

Mitigation: Define and test RLS policies in a dedicated Supabase migration script as part of this epic. Create integration tests that execute against a local Supabase instance with RLS enabled, covering insert, select by peer mentor ID, and denial of cross-mentor access.

Contingency: Maintain a fallback service-role client path (server-side only) that can be activated via a feature flag if client-side RLS is blocking legitimate operations while policies are corrected.

medium impact low prob technical

SharedPreferences on Flutter can become corrupted if the app crashes mid-write or if the device runs out of storage. A corrupted last-used activity type preference would cause the defaults manager to return null or an invalid ID, breaking the zero-interaction happy path.

Mitigation & Contingency

Mitigation: Wrap all LocalStorageAdapter reads in try/catch with typed safe defaults. Validate the retrieved activity type ID against the known list before returning it. Use atomic write operations where the platform supports them.

Contingency: If the preference store is corrupted, silently reset to the hardcoded default (first activity type alphabetically or 'general') and log a warning. The user loses their last-used preference but the app remains functional.