Implement TerminologyCacheAdapter with SharedPreferences
epic-dynamic-terminology-and-labels-foundation-task-002 — Build the TerminologyCacheAdapter as a data-layer class that persists a per-organization Map<String, String> label map to SharedPreferences. Implement write(orgId, labels), read(orgId) returning nullable map, and clear(orgId). Serialize the map as JSON string. This is the offline-first cache that ensures peer mentors from Blindeforbundet conducting home visits in low-connectivity areas always see correct organizational terminology.
Acceptance Criteria
Technical Requirements
Implementation Notes
Use `SharedPreferences.getInstance()` lazily — do not store the instance as a field if the class is long-lived, as the singleton can be invalidated in tests. Instead, call `SharedPreferences.getInstance()` at the start of each method or inject it via constructor for testability. Cache key format: `terminology_cache_v1_{sanitizedOrgId}` — include a version prefix (`v1`) so future cache format changes can be invalidated cleanly by bumping the version. For JSON encoding, cast the decoded value carefully: `(jsonDecode(raw) as Map
This adapter will be replaced or extended by the Hive backend in task-003, so keep the interface clean and avoid SharedPreferences-specific types leaking into the public API.
Testing Requirements
Unit tests using flutter_test with a mocked SharedPreferences instance (use `SharedPreferences.setMockInitialValues({})` in setUp). Test cases: (1) write then read returns identical map, (2) read on empty cache returns null, (3) write for org A does not affect read for org B, (4) clear(orgA) removes orgA but orgB still readable, (5) corrupt JSON string in SharedPreferences causes read to return null without throwing. All tests are fast (no real I/O). Use `group()` to organize by method name.
The labels JSONB column in organization_configs may lack a consistent schema across organizations, causing deserialization failures or silent missing keys when a new organization is onboarded with a differently structured map.
Mitigation & Contingency
Mitigation: Define and enforce a canonical JSONB schema via a Supabase check constraint and a migration script. Validate the schema in TerminologyRepository at parse time and emit structured errors for any key-type mismatches.
Contingency: If schema drift is discovered post-deployment, LabelKeyResolver's fallback logic ensures the app continues to function with English defaults while a data migration is prepared to normalize the offending organization's config.
Device local storage corruption or platform-specific SharedPreferences serialization bugs could render a cached terminology map unreadable, causing the app to fall back to English defaults unexpectedly for an organization with custom terminology.
Mitigation & Contingency
Mitigation: Wrap all cache reads in try/catch, validate the deserialized map against a minimum-required-keys check, and evict corrupted entries automatically before re-fetching from Supabase.
Contingency: Surface a non-blocking in-app warning to the coordinator that terminology may be in default English until the next sync completes; trigger an immediate background sync.