critical priority low complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

LocalStorageAdapter exposes an abstract interface (abstract class or interface) ILocalStorageAdapter with at minimum: readString(String key), writeString(String key, String value), readBool(String key), writeBool(String key, bool value), delete(String key), clear() — all returning Future<T>
SharedPreferencesLocalStorageAdapter implements ILocalStorageAdapter using the shared_preferences Flutter package
readString() returns null (not throws) when the key does not exist
readBool() returns null (not throws) when the key does not exist
writeString() and writeBool() return Future<void> and complete successfully without throwing on valid input
delete() removes the key and returns Future<void>; calling delete() on a non-existent key does not throw
clear() removes all keys managed by this adapter and returns Future<void>
The adapter class is injectable via constructor (SharedPreferences instance injected, not created internally) so tests can pass a fake implementation
No static state or singletons — every instance is independent
Located at lib/core/storage/local_storage_adapter.dart (interface) and lib/core/storage/shared_preferences_local_storage_adapter.dart (implementation)

Technical Requirements

frameworks
Flutter
shared_preferences
performance requirements
Read operations must complete in under 5ms for typical key-value data — SharedPreferences loads synchronously after init, so reads should be near-instant
Adapter initialization (SharedPreferences.getInstance()) should be called once at app startup via dependency injection, not on every read
security requirements
Do NOT store sensitive data (tokens, personnummer, health data) through this adapter — it uses SharedPreferences which is not encrypted. Document this constraint clearly in a code comment at the class level
For sensitive data, a separate SecureStorageAdapter using flutter_secure_storage must be used instead — this adapter is for non-sensitive preferences only

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

SharedPreferences.getInstance() is async and must be awaited at app startup. Inject the resolved SharedPreferences instance into the adapter constructor — do not call getInstance() inside adapter methods. Define the ILocalStorageAdapter abstract class in the same file as, or a separate file from, the implementation to allow mocktail to generate mocks with @GenerateMocks. Consider whether the project already has a storage abstraction — if so, extend it rather than creating a parallel one.

For the clear() method, consider scoping it with a key prefix (e.g., 'activity_reg_') rather than wiping all SharedPreferences, to avoid accidentally clearing unrelated app preferences.

Testing Requirements

Unit tests using flutter_test with a FakeSharedPreferences or mock. Test writeString then readString returns the written value. Test readString on missing key returns null. Test delete removes a written key so subsequent readString returns null.

Test clear() removes all previously written keys. Test that the adapter does not throw when delete() or clear() are called on an empty store. Integration test (optional, device-only): verify round-trip persistence survives hot restart using the real SharedPreferences.

Component
Local Storage Adapter
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.