high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

The search text field is debounced with a 300ms delay before filtering the chapter list
Only chapters not already present in the contact's current affiliations appear in the list
Chapters that are already pending addition (tracked in cubit) are also excluded from the search results
Tapping a chapter item calls ChapterMembershipCubit.trackPendingAdd(chapterId) exactly once
After trackPendingAdd is called, the selected chapter disappears from the available list immediately (optimistic UI)
When the total pending additions plus existing affiliations reaches 5, a non-dismissible banner appears with the text explaining the 5-chapter NHF maximum in plain language
The banner remains visible until the pending count drops below the maximum
Search field is cleared and list resets to showing all remaining available chapters when the user taps a clear icon
Empty search results show a 'No chapters found' message
All interactive elements meet 48x48dp minimum tap target size
Design System v3 tokens are used for all colors, spacing, and typography

Technical Requirements

frameworks
Flutter
BLoC
flutter_bloc
apis
ChapterMembershipCubit.trackPendingAdd(chapterId)
ChapterRepository.getAvailableChapters()
data models
Chapter
ChapterAffiliation
ChapterMembershipState
performance requirements
Debounce interval of 300ms on search input to avoid redundant filter operations
List filtering must complete synchronously for up to 1 400 chapters (NHF has up to 1 400 local chapters)
ListView.builder must be used (not ListView with children) for efficient rendering of large chapter lists
security requirements
Available chapter list must be scoped to chapters the coordinator has permission to assign — enforce via repository layer, not just UI filtering
Pending add operations must be validated against the 5-chapter rule server-side at save time, not only in the UI
ui components
ChapterSearchField (debounced AppTextField with clear button)
AvailableChapterListItem (tappable row: chapter name + region label)
MaxChaptersBanner (non-dismissible warning banner)
EmptySearchResultsMessage

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use a StreamController with debounce (from rxdart or manual Timer) for the search field to avoid filtering on every keystroke — 300ms is the standard for search fields. Compute the filtered list inside a BlocBuilder using a combination of the cubit's availableChapters list and the search query; keep the query in local widget state (StatefulWidget) since it is transient UI state, not domain state. For the 5-chapter rule: derive the effective count as (existingActiveAffiliations.length + pendingAdds.length) and show the banner when this equals or exceeds 5. The banner must use a non-dismissible design (no close button) — use a styled Container with a warning color token and an icon.

ListView.builder is mandatory given that NHF has up to 1,400 local chapters. Use an itemExtent on ListView.builder to improve scroll performance with large lists.

Testing Requirements

Write unit tests using bloc_test to verify: (1) trackPendingAdd emits the correct updated state, (2) the 5-chapter limit is enforced in the cubit (no-op or error state beyond 5). Write widget tests using flutter_test: (1) mount ChapterAssignmentEditor with a mock cubit emitting a known state, type in search field and assert list filters correctly, (2) assert already-affiliated chapters are absent from the list, (3) tap a chapter and assert trackPendingAdd is called with the correct ID, (4) simulate pending count reaching 5 and assert the MaxChaptersBanner appears, (5) assert banner disappears when count drops below 5. Test debounce behavior by advancing the fake timer 299ms and asserting no filter, then 300ms and asserting filter applied. Target 90% branch coverage.

Component
Chapter Assignment Editor
ui medium
Epic Risks (3)
high impact medium prob technical

The Duplicate Activity Warning Dialog must announce itself to VoiceOver and TalkBack immediately on appearance. Flutter's default modal semantics do not guarantee focus shift to the dialog on all platform versions, risking silent appearance for screen reader users — a WCAG 2.2 failure.

Mitigation & Contingency

Mitigation: Wrap the dialog content in a Semantics node with liveRegion: true and explicitly request focus via FocusScope.of(context).requestFocus() on the dialog's primary action button in the post-frame callback. Test on physical iOS (VoiceOver) and Android (TalkBack) devices, not only simulators.

Contingency: If automatic focus fails on a specific platform version, add a platform-specific fallback using SemanticsService.announce() to force a live region announcement of the dialog's headline text.

medium impact medium prob technical

The Chapter Membership Cubit tracks pending changes before commit to support the two-step add/confirm flow. If the user navigates away mid-edit or the app is backgrounded, uncommitted pending state could be replayed incorrectly on return, causing phantom affiliation additions or removals.

Mitigation & Contingency

Mitigation: Design the Cubit to hold pending changes only in transient in-memory state with no persistence. On any navigation-away event, emit a reset state that discards pending changes. Prevent accidental navigation during an active edit by showing a discard-changes confirmation dialog.

Contingency: If state desync is reported in production, add an explicit state reconciliation step in the Cubit's onResume handler that re-fetches the authoritative affiliation list from the repository and resets all pending state before re-rendering.

medium impact high prob technical

The Chapter Assignment Editor's searchable chapter list must load quickly. If the organisation has hundreds of chapters (NHF has 1,400 local chapters) and the full list is fetched on dialog open, the editor will be slow to display and the search will be sluggish.

Mitigation & Contingency

Mitigation: Scope the chapter list to only chapters within the coordinator's administrative scope (not all 1,400), leveraging the existing hierarchy access scope service. Implement server-side search with a minimum 2-character threshold and debounce to avoid excessive Supabase queries.

Contingency: If the scoped list is still too large, add local caching of the chapter list with a 15-minute TTL and an explicit refresh button, ensuring the editor is always responsive even on poor network conditions.