high priority medium complexity backend pending fullstack developer Tier 5

Acceptance Criteria

On form submit, pre-save validation runs synchronously against the in-memory tree before any Supabase write is attempted
If cycle detection fails, the save is aborted, a BLoC validation error state is emitted, and no network call is made
If depth-limit validation fails, the save is aborted with an inline error; no network call is made
On successful validation, an optimistic UI update immediately adds/updates the node in the local tree before the Supabase write completes
If the Supabase write fails (network error or RLS rejection), the optimistic update is rolled back and the original tree state restored within 500ms
Create mode posts to organization_units with a generated UUID; edit mode patches the existing row by ID — these are distinct code paths with distinct BLoC events
On successful save, the editor closes and the parent tree view reflects the updated node without a full reload
BLoC emits HierarchyNodeEditorSaveSuccess with the persisted node model on success
BLoC emits HierarchyNodeEditorSaveFailure with a user-readable error message on Supabase failure
Debounced parent dropdown search triggers a fresh Supabase query at most once per 300ms during rapid typing
All Supabase operations are scoped to the authenticated user's organization_id via RLS; cross-org writes are blocked at the database level
Edit mode correctly detects if the user changed the parent to a descendant of the current node and surfaces a cycle error before attempting save

Technical Requirements

frameworks
Flutter
BLoC
flutter_test
apis
Supabase PostgREST: POST /organization_units (create)
Supabase PostgREST: PATCH /organization_units?id=eq.{id} (update)
Supabase PostgREST: GET /organization_units?organization_id=eq.{id}&select=id,name,level_type,parent_id (tree load for validation)
data models
contact_chapter
contact
performance requirements
Optimistic update must render within one frame of the user tapping save
Rollback must complete within 500ms of detecting a Supabase write failure
Pre-save validation (in-memory cycle check) must complete in under 50ms for trees up to 1,400 nodes
security requirements
organization_id always derived from the authenticated JWT claims, never from client-supplied form data
RLS policies on organization_units enforce that users can only write nodes belonging to their organization
Node IDs generated client-side use uuid v4 (dart:uuid); server validates uniqueness via primary key constraint
ui components
HierarchyNodeEditorBloc (events: SaveRequested, ParentSearchQueryChanged; states: Saving, SaveSuccess, SaveFailure, ValidationError)
HierarchyStructureValidator (pure Dart, no Flutter dependency — unit testable in isolation)

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Separate the BLoC into two event handlers: _onCreateNode and _onUpdateNode. Both call HierarchyStructureValidator.validate() first with the full in-memory tree snapshot (loaded when the form opens). For the optimistic update pattern: (1) emit an intermediate state with the tentative node added to the local tree, (2) attempt the Supabase write, (3) on success emit SaveSuccess, (4) on failure emit SaveFailure and re-emit the pre-optimistic tree snapshot stored before step 1. Use Supabase's upsert only if the create/edit distinction is not important — here they are distinct, so use insert and update separately to keep error handling clean.

The parent dropdown debounce should be implemented as a BLoC event: ParentSearchQueryChanged(query) — the BLoC handles debounce internally using Stream.debounceTime from rxdart (already likely in the project). Avoid placing debounce logic in the widget layer. For the in-memory tree cycle check, implement as a depth-first traversal starting from the proposed parent and checking if the current node's ID appears as an ancestor.

Testing Requirements

Unit tests (flutter_test): test HierarchyNodeEditorBloc save flow for create mode — mock Supabase client, assert SaveSuccess emitted on mock success and SaveFailure on mock error; test rollback by asserting tree state reverts after mock write failure; test cycle detection by constructing an in-memory tree with a known cycle and asserting ValidationError emitted before any write. Unit tests for HierarchyStructureValidator: test all cycle cases (direct parent = self, grandparent loop, multi-hop cycle) and all depth-limit violations. Integration tests: run against Supabase test instance — create a node, verify it appears in the database; edit a node, verify patch applied; attempt cross-org write, verify RLS blocks it. Target 90%+ branch coverage on BLoC and validator.

Component
Hierarchy Node Editor Screen
ui 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.