critical priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Six state classes are defined: ChapterMembershipInitial, ChapterMembershipLoading, ChapterMembershipLoaded, ChapterMembershipSaving, ChapterMembershipSuccess, ChapterMembershipError
All state classes extend a common sealed base class `ChapterMembershipState` (or use freezed union) — pattern matches exhaustively in switch expressions
ChapterMembershipLoaded carries: `List<ChapterAffiliation> currentAffiliations`, `List<String> pendingAddChapterIds`, `List<String> pendingRemoveAffiliationIds` — all immutable (final, unmodifiable lists)
ChapterMembershipError carries a typed `ChapterMembershipFailure` (sealed class or enum) covering: NetworkFailure, MaxChaptersExceeded, NotFound, PermissionDenied, Unknown
ChapterMembershipSuccess optionally carries the updated list of affiliations to allow immediate UI refresh without a re-fetch
State classes implement `==` and `hashCode` correctly (via freezed or manual override) so `BlocBuilder` rebuilds only on actual state changes
All state classes are in a dedicated file: `lib/features/chapter_membership/cubit/chapter_membership_state.dart`
State classes follow the same naming and structural convention as other Cubits in the project (consistent with existing Bloc/Cubit files)

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
freezed (if used by project convention)
data models
ChapterAffiliation
ChapterMembershipFailure
performance requirements
State objects must be immutable — no mutable list fields
copyWith pattern (via freezed or manual) must allow efficient state updates without full reconstruction
security requirements
State classes must not hold sensitive personal data beyond what is necessary for UI rendering (e.g., no full contact records, only IDs and display names)
ui components
ChapterMembershipLoaded state fields drive the chapter selection list UI
ChapterMembershipError.failure type drives error message display

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

If the project uses freezed, generate the state union with `@freezed` and run `dart run build_runner build`. If not using freezed, implement a manual sealed class hierarchy using Dart 3 `sealed class` keyword — all subclasses in the same file. Prefer Dart 3 sealed classes over abstract + subclass pattern for compile-time exhaustiveness in switch. ChapterMembershipLoaded.pendingAddChapterIds and pendingRemoveAffiliationIds should be `List` (not `Set`) to preserve insertion order for UI display, but wrap them as `List.unmodifiable(...)` in the constructor.

ChapterMembershipFailure should be a separate sealed class (not mixed into ChapterMembershipError) so it can be reused in future service-layer error handling. Keep the state file focused: no business logic, no service imports — pure data containers only.

Testing Requirements

Unit tests (flutter_test): verify equality — two ChapterMembershipLoaded instances with identical data are equal; two with different pendingAddChapterIds are not equal. Verify ChapterMembershipFailure discriminates all error types correctly. Verify copyWith on ChapterMembershipLoaded updates only the specified field. These are pure Dart tests with zero dependencies — fast and deterministic.

Aim for 100% coverage of state class constructors and equality logic.

Component
Chapter Membership Cubit
service 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.