high priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

ActiveChapterCubit exposes a Stream<OrganizationalUnit?> that emits the currently active chapter whenever it changes
ChapterSelected event triggers a state transition within 100ms and persists the selection to secure local storage using flutter_secure_storage
On cold start, the cubit restores the last persisted active chapter from secure storage before emitting its first state
When the active chapter changes, all registered downstream listeners (e.g., activity repository, reporting service) receive a ChapterChanged notification within one event cycle
If the persisted chapter ID no longer exists in the user's current assignment list, the cubit emits ActiveChapterState.none and clears the stored value
Switching chapters while a background operation is in progress does not corrupt in-flight requests; operations complete against the chapter context they started with
The cubit correctly handles a user assigned to 0 chapters (emits empty state) and to exactly 1 chapter (auto-selects without user prompt)
Unit tests cover: initial load from storage, chapter switch, invalid persisted ID recovery, and downstream notification dispatch

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
flutter_secure_storage
apis
Supabase Realtime (listen for assignment revocations)
Local secure storage API
data models
OrganizationalUnit
UserUnitAssignment
ActiveChapterState
performance requirements
State transition latency under 100ms on mid-range devices
Storage read on cold start completes before first frame renders (use FutureBuilder/init guard)
No unnecessary Supabase round-trips on chapter switch — use cached assignment list
security requirements
Active chapter ID persisted only in flutter_secure_storage, never in SharedPreferences or plain files
Chapter context validated server-side on every Supabase query via RLS — client-side state is convenience only
Cubit does not store or log any PII beyond the chapter UUID

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use Cubit (not full BLoC) since chapter switching is a simple command with no complex event sequencing. Define a sealed ActiveChapterState with three variants: ActiveChapterInitial, ActiveChapterLoaded(OrganizationalUnit unit), and ActiveChapterEmpty. Downstream notification should use a dedicated ChapterChangeNotifier interface rather than direct service coupling — register listeners via dependency injection to keep the cubit testable. For storage key, use a namespaced key like 'nhf.active_chapter.' so switching users on the same device clears the correct entry.

Guard the cubit's close() method to also clear in-memory listeners to prevent memory leaks during logout flows.

Testing Requirements

Unit tests (flutter_test) must cover all cubit state transitions: initial empty state, restore from secure storage (mock flutter_secure_storage), chapter selection, chapter switch, handling of revoked chapter ID, and zero-chapter edge case. Integration test verifying that downstream service mock receives ChapterChanged event after cubit state update. Minimum 90% branch coverage on the cubit class. No golden/widget tests required for this task.

Component
Unit Assignment Service
service medium
Epic Risks (3)
high impact medium prob technical

Recursive aggregation queries across four hierarchy levels (national → region → local) with 1,400 leaf nodes may be too slow for real-time dashboard requests, exceeding the 200ms target and causing spinner timeouts.

Mitigation & Contingency

Mitigation: Implement aggregation as a Supabase RPC using a single recursive CTE rather than multiple round-trip queries. Pre-compute aggregations nightly via a scheduled Edge Function and cache results. For real-time needs, aggregate only the immediate subtree on demand.

Contingency: Surface a 'Refreshing...' indicator and serve stale cached aggregations immediately. Queue an async recalculation and push updated data via Supabase Realtime when ready, avoiding blocking the admin dashboard.

medium impact medium prob scope

The 5-chapter limit and primary-assignment constraint are NHF-specific. Applying these rules globally may break HLF and Blindeforbundet configurations where different limits apply, requiring per-organization configuration that was not initially scoped.

Mitigation & Contingency

Mitigation: Make the maximum assignment count a configurable value stored in the organization's feature-flag or settings table rather than a hardcoded constant. Design the assignment service to read this limit at runtime per organization.

Contingency: Default the limit to a high value (e.g., 100) for organizations other than NHF, effectively making it non-restrictive, while keeping the enforcement logic intact for when per-org configuration is fully implemented.

medium impact low prob technical

The searchable parent dropdown in HierarchyNodeEditor must search across up to 1,400 units efficiently. Client-side filtering of the full hierarchy may be slow; server-side search adds complexity and latency.

Mitigation & Contingency

Mitigation: Use the in-memory hierarchy cache as the search corpus — since the cache already holds the flat unit list, client-side filtering with a debounced input is sufficient and avoids extra Supabase calls. Pre-build a search index on cache load.

Contingency: Cap the dropdown to showing the 50 most recently accessed units by default, with a 'search all' option that triggers a server-side full-text query. This keeps the common case fast while supporting edge cases.