high priority low complexity backend pending backend specialist Tier 3

Acceptance Criteria

When ChapterSelected state is emitted, the selected unit's ID is written to flutter_secure_storage under key active_chapter_{userId}
On app startup (before home screen renders), the Cubit reads the persisted chapter ID from flutter_secure_storage and dispatches ReloadFromStorage
ReloadFromStorage resolves the stored ID through HierarchyCache (or OrganizationUnitRepository as fallback) and emits ChapterSelected if found
If the persisted chapter ID no longer exists in the hierarchy (unit was deleted/archived), the Cubit emits NoChapterSelected and clears the stale persisted value
On ClearChapter or logout event, the persisted chapter ID is deleted from flutter_secure_storage synchronously before emitting NoChapterSelected
Storage key is namespaced by userId (not just organizationId) to support multiple users on the same device
flutter_secure_storage operations use IOSOptions(accessibility: KeychainAccessibility.first_unlock) on iOS and EncryptedSharedPreferences on Android
No chapter ID is stored in plain SharedPreferences — only flutter_secure_storage is used for this value
If flutter_secure_storage read fails (e.g., first launch after app reinstall on iOS), the failure is caught and treated as no persisted chapter

Technical Requirements

frameworks
Flutter
BLoC
flutter_secure_storage
apis
HierarchyCache (internal) — getNode for fast resolution
OrganizationUnitRepository — fallback if cache is not yet populated at startup
data models
contact_chapter (OrganizationUnit — id field persisted as UUID string)
performance requirements
flutter_secure_storage read at startup must not block the splash screen for more than 200ms
Chapter restoration must complete before the first role-dependent screen renders
security requirements
Use flutter_secure_storage (iOS Keychain / Android Keystore) — never plain SharedPreferences for chapter ID
Storage key namespaced by userId to prevent cross-user data access on shared devices
On logout, secure storage entry must be deleted before any navigation away from authenticated screens
flutter_secure_storage iOS option: KeychainAccessibility.first_unlock to allow background restoration

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Inject a FlutterSecureStorage instance into the Cubit constructor for testability — do not instantiate it internally. Create a thin ChapterStorageService abstraction around flutter_secure_storage to isolate the key naming logic and make mocking trivial. Key pattern: 'active_chapter_${userId}'. On startup orchestration: the app's root widget (or an AppStartupProvider) should await cubit.restoreFromStorage() before routing to the home screen — use a FutureProvider in Riverpod to gate navigation.

Handle the iOS first-launch keychain loss edge case: wrap the read in try/catch and treat PlatformException as empty storage. Do not await the write in SelectChapter — fire-and-forget with unawaited() to keep state transitions synchronous.

Testing Requirements

Unit tests (flutter_test + bloc_test): (1) SelectChapter triggers a write to mock flutter_secure_storage with correct key and value. (2) ReloadFromStorage reads from mock storage, resolves unit via mock HierarchyCache, emits ChapterSelected. (3) ReloadFromStorage with stale (deleted) unit ID emits NoChapterSelected and clears storage. (4) Logout event deletes the storage key and emits NoChapterSelected.

(5) flutter_secure_storage read failure is caught gracefully. Mock flutter_secure_storage using the package's built-in test mock or a manual fake implementing FlutterSecureStorageInterface. Verify storage.delete() is called on logout in the correct order (before state emission).

Component
Active Chapter State (BLoC)
service low
Epic Risks (3)
high impact medium prob technical

Recursive CTE queries for large hierarchies (1,400+ nodes) may exceed Supabase query timeouts or produce unacceptably slow responses, degrading tree load time beyond the 1-second target.

Mitigation & Contingency

Mitigation: Implement Supabase RPC functions for subtree fetches rather than client-side recursive calls. Use materialized path or closure table as a supplemental index for depth-first traversal. Benchmark with realistic NHF data volumes during development.

Contingency: Fall back to a pre-computed flat unit list stored in the hierarchy cache with client-side tree reconstruction, trading freshness for speed. Add a background refresh job to keep the cache warm.

medium impact low prob technical

Concurrent writes from multiple admin sessions could cause cache staleness, leading to stale tree views and incorrect ancestor path computations that corrupt aggregation results.

Mitigation & Contingency

Mitigation: Use optimistic versioning on cache entries with a short TTL (5 minutes) as a safety net. Subscribe to Supabase Realtime on the organization_units table to push invalidation events to all connected clients.

Contingency: Provide a manual 'Refresh Hierarchy' action in the admin portal that forces a full cache bust, and display a staleness warning banner when the cache age exceeds the TTL.

high impact low prob security

Persisting the flat unit list to local storage may expose organization structure data if the device is compromised or the storage is not properly encrypted, violating data protection requirements.

Mitigation & Contingency

Mitigation: Use flutter_secure_storage (AES-256 backed by Keychain/Keystore) for the local unit list cache rather than SharedPreferences. Include only unit IDs, names, and types — no member PII.

Contingency: Disable local-storage persistence entirely and rely on in-memory cache only. Accept the trade-off of no offline hierarchy access for the security guarantee.