high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Widget exposes a boolean `editMode` parameter; when false, renders read-only chip list as per task-013 baseline
In edit mode, all available chapters are presented as a scrollable selectable list below or overlaying existing chips
Selecting a chapter that is not yet affiliated adds it and emits a ChapterSelectionChanged event with the updated list
Deselecting an already-affiliated chapter removes it and emits a ChapterSelectionChanged event
When 5 chapters are selected, all unselected chapter items are visually disabled and not tappable
An inline error/info message reading 'Maximum 5 chapter affiliations allowed (NHF rule)' appears when the cap is reached
The inline message disappears immediately when a chapter is deselected below the cap
ChapterSelectionChanged carries a typed payload: `List<ChapterId> selectedChapterIds` and `bool isAtCapacity`
Disabled chapter items pass WCAG 2.2 AA contrast requirements (disabled state color from design tokens, not hardcoded)
Selection interaction uses ink splash / ripple consistent with design token touch feedback
Widget does not manage its own persistence — state is lifted to parent via the event
Empty available-chapters list shows a 'No chapters available' empty state, not an error
Keyboard and screen-reader (TalkBack/VoiceOver) can toggle selection and receive meaningful semantic labels

Technical Requirements

frameworks
Flutter
BLoC
flutter_test
data models
ChapterId
ChapterAffiliation
ChapterSelectionChanged
performance requirements
Widget rebuild is scoped to chip list only — use const constructors for static labels
List of available chapters rendered with ListView.builder if count > 10 to avoid jank
No Supabase call inside this widget — chapter list is injected via constructor
security requirements
Chapter IDs passed through the event must be validated strings — do not interpolate raw user input into queries
Disabled state enforcement must be done in widget logic, not only in UI opacity, to prevent programmatic bypass
ui components
MultiChapterAffiliationChip (extended)
ChapterSelectableListItem
InlineCapWarningBanner
DesignToken color/spacing references for disabled and selected states

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Introduce an `editMode` boolean prop and an `onSelectionChanged` callback of type `void Function(ChapterSelectionChanged)`. Internally track selected IDs in a local `Set` passed down from a StatefulWidget wrapper or from the BLoC — prefer keeping this widget stateless and receiving `selectedIds` + `availableChapters` as inputs so the BLoC owns truth. The cap constant (`maxAffiliations = 5`) must be imported from `contact-form-validator` constants — never hardcode 5 in this widget. For the disabled state, wrap each unselected `ChapterSelectableListItem` with `IgnorePointer(ignoring: isAtCapacity)` and set `semanticsLabel` to 'Disabled, maximum affiliations reached'.

Use `AnimatedSwitcher` for the inline warning banner to animate in/out smoothly. Avoid `GestureDetector` on top of `InkWell` — use only `InkWell` for correct ripple behavior. Follow the organization labels system so 'chapter' terminology can be overridden per org via the labels map.

Testing Requirements

Widget tests are handled in task-015. For this task: manually verify on both iOS (VoiceOver) and Android (TalkBack) that the cap enforcement reads aloud as 'Maximum affiliations reached, this item is disabled'. Verify that tapping a disabled item produces no state change. Run `flutter analyze` and confirm zero lints before PR.

Integration with contact-form-validator NHF rule must be traced by reading the validator source and confirming the cap constant is sourced from there (not duplicated).

Epic Risks (3)
high impact medium prob security

Blindeforbundet's encryption key retrieval mechanism may not be finalised at implementation time, or session key availability via Supabase RLS may be inconsistent, causing decryption failures that expose masked placeholders to users and degrade the experience.

Mitigation & Contingency

Mitigation: Agree with Blindeforbundet on key storage and retrieval contract before implementation starts. Prototype key retrieval in a spike against the staging Supabase instance and validate the full decrypt/verify cycle with real test data before committing to the implementation.

Contingency: Implement a fallback that shows a 'field temporarily unavailable' state with a retry affordance. Log decryption failures server-side for audit. Escalate to Blindeforbundet stakeholders to unblock key management before the service tier epic begins.

medium impact medium prob technical

NHF contacts may belong to up to 5 chapters, each governed by separate RLS policies. A coordinator's chapter scope may not cover all affiliations, causing partial profile reads or silent data omissions that are difficult to detect in tests.

Mitigation & Contingency

Mitigation: Map all RLS policy combinations for multi-chapter contacts early. Write integration tests that create contacts with 5 affiliations and query them from coordinators with varying chapter scopes. Use Supabase's RLS test utilities to verify row visibility per role.

Contingency: Add an explicit 'affiliation partially visible' state in the repository response model so the UI can communicate scope limitations to the coordinator rather than silently showing incomplete data.

low impact medium prob scope

Organisation-specific validation rules (e.g., NHF chapter limit, Blindeforbundet encrypted field edit flow) may expand in scope during implementation as edge cases are discovered, causing the validator to grow beyond the planned complexity.

Mitigation & Contingency

Mitigation: Define the complete validation rule set with product and org stakeholders before coding begins. Document each rule with its source organisation and acceptance test. Use a rule registry pattern so new rules can be added without modifying core validator logic.

Contingency: Timebox validator enhancements to 2 hours per additional rule. Defer non-blocking rules to a follow-on maintenance task rather than blocking the epic delivery.