high priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

Form renders as a full-screen widget with a node name TextField, a level type SegmentedButton (chapter/region/national), and a searchable parent dropdown
Parent dropdown lazy-loads organizational units in paginated batches of 20; a loading indicator appears while fetching and an empty-state message shows when no results match
Searching in the parent dropdown debounces input by 300ms before querying the backend, preventing excessive API calls
Inline validation error for cycle detection appears beneath the parent dropdown within 200ms of selecting a parent that would create a cycle
Inline validation error for depth-limit violation (exceeding 4 levels: national > region > chapter > sub-chapter) appears beneath the level selector when the chosen level conflicts with the selected parent
All form fields have Semantics labels readable by TalkBack and VoiceOver; focus order follows top-to-bottom logical flow
Error messages are announced via Semantics.liveRegion so screen readers speak them without user interaction
Form is scrollable on small-screen devices (minimum supported: 360×640dp) without content overflow
Node name field enforces a maximum of 120 characters with a live character counter
Submit button is disabled while validation is in progress and shows a CircularProgressIndicator inside the button during async validation
Tapping outside a dropdown closes it and returns focus to the trigger widget
Form correctly pre-populates all fields in edit mode using the existing node's data
Color contrast of all text and interactive elements meets WCAG 2.2 AA (4.5:1 for body text, 3:1 for large text and UI components)

Technical Requirements

frameworks
Flutter
BLoC
flutter_test
apis
Supabase PostgREST (organization_units table, filtered by organization_id and level_type)
data models
contact_chapter
contact
performance requirements
Parent dropdown search results render within 300ms of debounce completion on a mid-range device
Form initial render completes within one frame (16ms) with no jank on node open
Lazy-load pagination must not block the UI thread; use compute() or isolate for large list processing if needed
security requirements
Organization_id injected from authenticated session context — never accepted from form input
All Supabase queries filtered by organization_id enforced by RLS; no cross-org data leakage possible
Node name sanitized with .trim() and HTML-escaped before display to prevent XSS in web targets
ui components
HierarchyNodeEditorForm (StatefulWidget, full-screen)
LevelTypeSelector (SegmentedButton wrapping NodeLevelType enum)
ParentUnitSearchDropdown (lazy-loading, debounced SearchDelegate pattern)
InlineValidationMessage (Semantics liveRegion wrapper around Text)
SubmitButton (loading-state-aware AppButton variant)

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Use a BLoC (HierarchyNodeEditorBloc) with states: HierarchyNodeEditorInitial, HierarchyNodeEditorLoading, HierarchyNodeEditorValidationError (carries Map fieldErrors), HierarchyNodeEditorReady. The parent dropdown should use a StreamController with debounceTime(Duration(milliseconds: 300)) feeding into a Supabase .select().ilike('name', '%query%').limit(20).range() query. For cycle detection inline feedback, call the Hierarchy Structure Validator's pure-Dart method synchronously against the in-memory loaded tree (avoid a round-trip for this check). Depth-limit validation is a pure function: given the selected parent's depth, the proposed level's expected depth must equal parent_depth + 1.

Use Flutter's FocusTraversalGroup to enforce logical focus order. Wrap all error Text widgets in Semantics(liveRegion: true, child: ...) for proper screen-reader announcement. In edit mode, initialize BLoC with a HierarchyNodeEditorEditRequested(nodeId) event that pre-loads the node and populates form fields. Avoid StatefulWidget local state beyond TextEditingControllers — push all validation state into BLoC.

Testing Requirements

Unit tests (flutter_test): test LevelTypeSelector emits correct BLoC events on selection; test ParentUnitSearchDropdown debounce logic with fake timers (FakeAsync); test InlineValidationMessage renders correct text for each validation error type. Widget tests: pump HierarchyNodeEditorForm in both create and edit modes and verify field pre-population; verify submit button disabled state during async validation; verify focus traversal order using FocusNode.nextFocus() assertions. Accessibility tests: use flutter_test's SemanticsController to assert all fields have labels, error messages have liveRegion, and contrast ratios pass. Integration tests (on device/emulator): verify parent dropdown lazy-load triggers on scroll, search filters results, and debounce suppresses calls.

Target 85%+ line coverage on form widget and child components.

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.