high priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

ChapterAffiliationsPanel renders correctly when ChapterMembershipCubit emits a loaded state with one or more affiliations
Each badge chip displays chapter name, role, and status as distinct labeled text elements within the chip
Badge chip foreground/background color combinations achieve a minimum 4.5:1 contrast ratio under WCAG 2.2 AA for all status variants (Active, Inactive, Pending)
Panel renders an empty-state placeholder message when the cubit emits a loaded state with zero affiliations
Panel renders a loading skeleton or spinner when the cubit emits a loading state
Panel renders an inline error message when the cubit emits an error state, without crashing
BlocBuilder rebuilds only the affected badges when state changes, avoiding full panel re-renders
Widget is scrollable when the number of badges exceeds the available vertical space
Design System v3 design tokens (colors, typography, spacing, radii) are used exclusively — no hardcoded style values
Widget passes flutter_test golden image test against approved design reference
Widget integrates cleanly in the contact detail screen without layout overflow on common device sizes (360dp–428dp width)

Technical Requirements

frameworks
Flutter
BLoC
flutter_bloc
apis
ChapterMembershipCubit (state stream)
Supabase (via repository layer)
data models
ChapterAffiliation
ChapterMembership
ContactChapterRole
performance requirements
BlocBuilder selector must use buildWhen to prevent unnecessary rebuilds
Badge list must render within 16ms frame budget for up to 5 badges
No synchronous database or network calls inside build()
security requirements
Display only affiliations the authenticated user has read permission for based on RBAC
Do not expose internal affiliation IDs in visible UI text
ui components
ChapterAffiliationsPanel (StatelessWidget, BlocBuilder consumer)
AffiliationBadgeChip (reusable chip widget: chapter name + role + status)
AffiliationsEmptyState (placeholder widget)
AffiliationsLoadingIndicator (skeleton widget)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use BlocBuilder with a buildWhen that checks for changes in the affiliations list only — not unrelated state fields. Model the badge chip as a separate stateless widget AffiliationBadgeChip accepting (chapterName, role, status) so it can be tested and reused independently. Map status enum values to Design System v3 color tokens (e.g., statusActiveColor, statusInactiveColor) defined in the design token file rather than hardcoded hex values. Use Wrap widget for the badge list so chips reflow naturally on narrow screens.

For contrast compliance, avoid placing colored text directly on the primary brand color backgrounds — use a neutral dark overlay or border approach for status badges. Implement the empty state as a Column with a subtle icon and a localized string key so copy can be updated without code changes.

Testing Requirements

Write unit tests using flutter_test and bloc_test: (1) verify BlocBuilder renders correct number of chips for a given loaded state, (2) verify empty-state widget appears when affiliations list is empty, (3) verify error-state widget appears on cubit error emission, (4) verify loading indicator appears during cubit loading state. Write widget tests that mount ChapterAffiliationsPanel with a mock cubit and assert chip text content matches affiliation data. Write golden tests for each status variant (Active, Inactive, Pending) in both light and dark mode. Use an accessibility audit tool (e.g., flutter_accessibility_service or manual contrast checker) to confirm all badge color combinations pass 4.5:1.

Target 90%+ branch coverage on the widget and badge chip.

Component
Chapter Affiliations Panel
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.