high priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

On a successful fetchMentorsInBoundingBox, the result is persisted to local storage with the current UTC timestamp as the cache write time
On network failure (SocketException, TimeoutException), the service reads from the local cache and returns the cached list if it exists and is within 24 hours of the write time
If the cache is stale (>24 hours) and network is unavailable, MentorLocationService throws a StaleOfflineCacheException containing the stale data and the cache write timestamp
The returned result type differentiates between a live fetch (FetchResult.live) and a cache hit (FetchResult.cached(lastUpdated: DateTime))
clearCache() deletes all cached mentor location data and the stored timestamp from local storage
Cache eviction on write: any cache entry older than 24 hours is automatically deleted before writing a new entry
Cached data is stored per organisation ID to prevent data leakage between organisations in multi-org scenarios
Unit test: mock network success → verify cache write occurs
Unit test: mock network failure with valid cache → verify cached list returned with CacheHitState
Unit test: mock network failure with stale cache → verify StaleOfflineCacheException thrown
Unit test: clearCache() called → subsequent cache read returns null
The cache storage key includes a version prefix (e.g., mentor_locations_v1_) to support future schema migrations via a key prefix bump

Technical Requirements

frameworks
Flutter
flutter_bloc
hive_flutter or shared_preferences
apis
Supabase Dart client (existing fetch pipeline)
data models
MentorLocation
CachedMentorLocationEntry (list + timestamp)
FetchResult (sealed: live / cached)
performance requirements
Cache read must complete in under 20 ms to avoid perceptible delay on cache-hit path
Cache write must be non-blocking — executed after the fetch result is returned to the caller using unawaited() or a fire-and-forget write
Serialisation of MentorLocation list to JSON must handle lists of up to 500 entries without UI jank
security requirements
Cached data must not include fields beyond what is needed for display (no sensitive contact details, phone numbers, or personal identifiers beyond mentor ID and display name)
Cache entries are stored in the app's sandboxed document directory — no external storage required or used
Cache is cleared on logout to prevent data persistence across user sessions

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Prefer Hive over shared_preferences for this use case — Hive supports typed adapters and is faster for list serialisation. Create a MentorLocationCacheAdapter (HiveObject) that stores the JSON-encoded list and an ISO-8601 timestamp string. Alternatively, if Hive is not already a project dependency, use shared_preferences with JSON.encode/decode and store the timestamp as a separate key (mentor_locations_v1_{orgId}_data and mentor_locations_v1_{orgId}_ts). Abstract the storage behind a MentorLocationCacheRepository interface so the concrete implementation can be swapped in tests.

The 24-hour TTL should be a configurable constant (kMentorLocationCacheTtl = const Duration(hours: 24)) in a constants file to allow future adjustment. Ensure clearCache() is wired into the logout flow in the auth layer.

Testing Requirements

Unit tests with flutter_test: mock the local storage adapter (Hive box or SharedPreferences) using a fake in-memory implementation. Test the full state machine: (1) first fetch — no cache exists, network succeeds, cache written; (2) second fetch — cache valid, network succeeds, live result returned and cache refreshed; (3) network failure — cache valid (<24h), cached result returned; (4) network failure — cache stale (>24h), exception thrown; (5) clearCache() — subsequent read returns null. Integration test: use flutter_test with a temporary Hive directory; verify serialisation round-trip preserves all MentorLocation fields. Verify organisation-scoped cache keys isolate data between two different org IDs.

Component
Mentor Location Service
service high
Epic Risks (2)
medium impact medium prob technical

The dual BLoC state machines (map view state + filter state) may introduce subtle synchronisation bugs where filter changes do not correctly re-trigger viewport queries, causing stale data to appear on the map.

Mitigation & Contingency

Mitigation: Define all BLoC state transitions in a state diagram before implementation. Use flutter_bloc's BlocObserver in development mode to log every state transition. Write explicit unit tests for filter-change → re-query transitions.

Contingency: If state synchronisation bugs appear in integration testing, refactor to a single unified BLoC that owns both map viewport state and filter state, eliminating cross-BLoC dependencies.

low impact medium prob scope

Cached mentor location data may become stale (mentors move, pause, or revoke consent) and coordinators in offline mode could be shown incorrect mentor information, leading to wasted outreach.

Mitigation & Contingency

Mitigation: Display a clear timestamp on cached data indicating when it was last synced. Set cache TTL to 24 hours and show an 'offline — data from [date]' banner. Revoked consent removes the mentor from the cache on next successful sync via contact-cache-sync-repository.

Contingency: If cache staleness causes user complaints, reduce TTL to 4 hours and implement background sync on app foreground. Accept that very-recently-revoked mentors may appear briefly in offline mode — document this as a known limitation in the privacy policy.