critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

ActiveChapterCubit (or BLoC) is implemented with three states: ChapterLoading, ChapterSelected(OrganizationUnit unit), NoChapterSelected
SelectChapter(unitId) event resolves the unit via HierarchyCache.getNode(unitId), emits ChapterLoading while resolving, then ChapterSelected on success or NoChapterSelected if unitId is not found
ClearChapter event immediately emits NoChapterSelected and clears any persisted chapter ID
ReloadFromStorage event triggers re-resolution of the last known chapter ID from storage; emits NoChapterSelected if storage is empty
The selected chapter is accessible app-wide via a Riverpod provider (activeChapterProvider) that exposes the current state as a stream
All widgets observing activeChapterProvider re-build correctly when chapter changes
Emitting ChapterSelected with a unit from a different organization_id than the authenticated user's org is rejected and triggers NoChapterSelected with an error log
BLoC/Cubit is properly closed and Riverpod provider disposed when the user logs out
State transitions are synchronous except for ReloadFromStorage which is async
SelectChapter with the same unitId as the current state is a no-op (no duplicate state emission)

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
HierarchyCache (internal) — getNode(unitId) for unit resolution
data models
contact_chapter (OrganizationUnit — id, name, parent_id, organization_id, type)
performance requirements
SelectChapter state transition must complete in under 50ms when unit is in HierarchyCache (O(1) lookup)
No network calls during SelectChapter — resolution is purely from in-memory cache
security requirements
Selected chapter must belong to the authenticated user's organization_id — reject cross-tenant unit IDs
Chapter ID persisted to storage must not contain sensitive PII — only the UUID string
ui components
ChapterSelectorWidget (consumer of activeChapterProvider — built in a separate task)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement as a Cubit rather than a full BLoC — state transitions are simple enough that events/handlers add unnecessary boilerplate. Define ActiveChapterState as a sealed class (Dart 3) with three subclasses. Expose the Cubit through a Riverpod StateNotifierProvider so both BLoC and Riverpod consumers can observe it — this is the standard bridge pattern. For cross-tenant guard: inject the current user's organizationId into the Cubit constructor (from auth state) and validate in selectChapter().

Use Equatable on all state classes to prevent duplicate emissions. The Riverpod provider should be overrideWithValue in tests for easy mocking.

Testing Requirements

Unit tests (flutter_test + bloc_test): (1) Initial state is NoChapterSelected. (2) SelectChapter with valid unitId emits [ChapterLoading, ChapterSelected]. (3) SelectChapter with unknown unitId emits [ChapterLoading, NoChapterSelected]. (4) ClearChapter from ChapterSelected emits NoChapterSelected.

(5) ReloadFromStorage with empty storage emits NoChapterSelected. (6) Cross-tenant unit ID is rejected. (7) Duplicate SelectChapter with same ID produces no second ChapterSelected emission. Use bloc_test package for state sequence assertions.

Mock HierarchyCache with a fake returning controlled responses.

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.