critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

ActivityRecord is a Dart class (or @immutable final class) with fields: id (String), activityTypeId (String), date (DateTime), durationMinutes (int), notes (String?), peerId (String), orgId (String), createdAt (DateTime), syncStatus (SyncStatus)
SyncStatus is a Dart enum with exactly three values: pending, synced, failed
ActivityRecord.fromJson(Map<String, dynamic>) correctly deserializes all fields including DateTime parsing from ISO-8601 strings and SyncStatus from string
ActivityRecord.toJson() returns a Map<String, dynamic> with all fields serialized; DateTime fields serialized as ISO-8601 strings; SyncStatus serialized as its name string
A static ActivityRecord.localOptimistic({required fields}) factory constructor generates an id with the 'local_' prefix followed by a UUID v4 and sets syncStatus to SyncStatus.pending
ActivityRecord implements equality (== and hashCode) based on the id field so that two records with the same id are considered equal regardless of other field values
ActivityRecord has a copyWith() method covering all fields
No dependency on Flutter framework — model is pure Dart, importable from both Flutter and any future server-side Dart code
The file is located at lib/features/activity_registration/data/models/activity_record.dart

Technical Requirements

frameworks
Dart
data models
activity
activity_type
performance requirements
fromJson/toJson must be O(1) — no recursive loops or lazy evaluation for standard record deserialization
security requirements
notes field must be nullable and treated as potentially sensitive — do not log its value anywhere in the model layer
id field must never be null — throw ArgumentError in factory if id is empty string

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the uuid Dart package (already likely in pubspec) for UUID generation. Keep the model in the data layer (not domain) since it contains serialization logic. If the project uses freezed or equatable, apply those annotations rather than hand-writing == and copyWith — check existing models in the codebase for the established pattern and match it. The 'local_' prefix convention is critical for the sync layer to distinguish optimistic records from server-confirmed records — document this contract in a code comment.

Avoid using dynamic in fromJson — cast explicitly and handle missing keys with null-coalescence for optional fields.

Testing Requirements

Unit tests in test/features/activity_registration/data/models/activity_record_test.dart. Test fromJson round-trip: serialize a known ActivityRecord to JSON and deserialize back, assert all fields are equal to original. Test localOptimistic() factory: assert id starts with 'local_', is a valid UUID after the prefix, and syncStatus == SyncStatus.pending. Test equality: two ActivityRecords with the same id but different notes are equal.

Test copyWith: changing a single field produces a new instance with only that field changed. Test fromJson handles null notes field without throwing.

Component
Activity Repository
data medium
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.