high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Each badge chip is wrapped in a Semantics widget with a label in the format '{ChapterName} chapter, {Role}, {Status}' (fully localized)
VoiceOver (iOS) reads the panel heading before any badge when focus enters the panel
TalkBack (Android) traversal order matches left-to-right, top-to-bottom visual order of badges
Decorative divider lines, background shapes, and icon-only decorations are wrapped with ExcludeSemantics
The panel heading is exposed as a semantic header node (Semantics(header: true, label: ...))
Empty-state message is announced as a single coherent sentence rather than fragmented words
Loading and error states provide descriptive live region announcements (Semantics(liveRegion: true))
No duplicate or redundant announcements occur when a badge contains both an icon and text for the same status
Passes SemanticsController.of(tester).nodesWith(label: ...) assertions in widget tests
Manual VoiceOver and TalkBack smoke tests pass without unexpected focus jumps

Technical Requirements

frameworks
Flutter
BLoC
data models
ChapterAffiliation
performance requirements
Semantics tree additions must not measurably increase frame render time (< 1ms overhead per chip)
security requirements
Semantic labels must not include internal database IDs or sensitive user data beyond what is visually displayed
ui components
Semantics (Flutter core widget)
ExcludeSemantics (Flutter core widget)
MergeSemantics (for composite chip elements)
AffiliationBadgeChip (updated with semantics annotations)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Wrap the entire panel in a Semantics(explicitChildNodes: true) container with a heading label so VoiceOver announces it as a navigation landmark. For each AffiliationBadgeChip, use MergeSemantics to collapse the internal text spans (chapter name, role, status) into one announcement — this avoids the screen reader reading three separate fragments. Format the merged label as an interpolated string: '${chapterName} chapter, ${role}, ${status}'. Apply ExcludeSemantics to any decorative Container or Icon that is purely visual.

Use Semantics(liveRegion: true) on the loading indicator and error message widgets so assistive technologies announce transitions automatically without requiring focus movement. Verify traversal order by reading the semantics node list from SemanticsController in tests — Flutter's default traversal follows widget tree order, so ensure the Wrap/Column order matches the visual order. Critically important for Blindeforbundet users who rely on VoiceOver as their primary interface.

Testing Requirements

Write widget tests using flutter_test and tester.semantics (SemanticsHandle): (1) assert that every rendered badge chip has a semantic label matching the expected format string, (2) assert that the panel heading node has header: true, (3) assert that decorative elements have no semantic label, (4) assert liveRegion: true on error and loading nodes. Use tester.pumpAndSettle() after cubit state transitions and re-assert semantic tree correctness. Perform manual accessibility testing on a physical iOS device with VoiceOver and an Android device with TalkBack, documenting traversal order. Include a test that verifies the empty-state label is a single non-empty string.

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.