critical priority low complexity frontend pending frontend specialist Tier 6

Acceptance Criteria

All list rows have a minimum touch target of 44×44 dp; the search bar input field has a minimum height of 44 dp
Selectable list rows use Semantics with inMutuallyExclusiveGroup: true, checked: <isSelected>, and a label composed of the mentor's name plus their metadata so screen readers announce the full row content
When the search result count changes, a live region (SemanticsProperties.liveRegion) announces the updated number of results to screen reader users (e.g., '3 mentors found')
The 'Continue' button's disabled state is announced correctly by screen readers (semanticsLabel includes 'disabled' context or Flutter's native disabled semantics are used)
The inline validation error message has a Semantics live region so it is announced automatically when it appears without requiring the user to navigate to it
Focus order: search bar → list items top to bottom → Continue button; verified via Focus traversal debugging
All labels and hints use plain language (Norwegian-friendly equivalents if the app is localised; English for code-level labels)
No accessibility issues remain after running Flutter's SemanticsDebugger on the screen
Manual TalkBack and VoiceOver tests pass for: searching, selecting a mentor, and proceeding

Technical Requirements

frameworks
Flutter
flutter_test
performance requirements
Live region announcements must not cause additional network requests or heavy computation
security requirements
Accessibility labels must not expose internal IDs or sensitive data
ui components
Semantics with inMutuallyExclusiveGroup, checked, label, hint for each selectable row
SemanticsProperties.liveRegion on the result-count announcement widget
SemanticsProperties.liveRegion on the inline error message widget
MergeSemantics to consolidate row content into a single focused announcement
FocusTraversalGroup with ReadingOrderTraversalPolicy for the full screen
ConstrainedBox / SizedBox for 44×44 dp touch target enforcement

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Radio group semantics in Flutter are achieved by wrapping each selectable row in Semantics(inMutuallyExclusiveGroup: true, checked: isSelected, ...) — do NOT rely on Radio widget if the design uses custom rows, as Radio's built-in semantics may conflict with MergeSemantics. For the live region result count, add a Semantics(liveRegion: true) wrapper around a Text widget showing 'X mentors found'; update this count via the same derived provider used for filtered list length. The error message live region follows the same pattern — Semantics(liveRegion: true) on the error Text so it announces immediately when it becomes visible. Cognitive accessibility for NHF: keep all labels concise and free of jargon; the mentor row label should read '[Name] — [area/status]' not internal codes.

Avoid relying on colour alone for the selected state — combine background highlight with a checkmark icon that is visible in both colour and high-contrast modes.

Testing Requirements

Widget tests: (1) pump screen with 3 mentors, read semantics tree and assert each row has inMutuallyExclusiveGroup: true and a non-empty label; (2) assert search bar has minimum height ≥ 44 dp via tester.getSize(); (3) simulate search query change and assert live region widget is present in the semantics tree; (4) simulate Continue tap with no selection and assert error live region is present. Manual tests: follow WCAG SC 1.3.1, 2.1.1, 2.4.3, 2.5.3, 4.1.2 checklists with TalkBack (Android 13+) and VoiceOver (iOS 16+). Document manual pass/fail in a checklist file committed to the repo.

Component
Peer Mentor Single Selector
ui low
Epic Risks (2)
medium impact medium prob technical

NHF coordinators may manage dozens of peer mentors across multiple chapters. If the multi-select list renders all contacts in a single unsorted ListView, performance degrades with 50+ items, and coordinators cannot efficiently locate a specific mentor, increasing the probability of selection errors and wrong-person proxy registrations.

Mitigation & Contingency

Mitigation: Use a SliverList with itemExtent for fixed-height rows to enable O(1) scroll position calculation. Implement the search filter using a debounce utility operating on an in-memory list (no extra API calls). Sort the contact list alphabetically by default. Add chapter-filter chips above the list for NHF's multi-chapter coordinators.

Contingency: If performance issues arise in testing with real data sets, introduce pagination with a 'load more' trigger at the bottom of the list and cache rendered rows using Flutter's AutomaticKeepAliveClientMixin.

high impact medium prob security

NHF has a complex 12-national-association / 9-region / 1,400-chapter hierarchy. It is ambiguous whether a coordinator can proxy-register for peer mentors outside their immediately assigned chapter. If the contact list is not correctly scoped by RLS, coordinators might see — and register on behalf of — peer mentors they do not manage, creating fraudulent activity records that skew Bufdir statistics.

Mitigation & Contingency

Mitigation: The Proxy Contact List Provider must query only peer mentors linked to the coordinator's own chapter scope via RLS. Add an explicit Supabase query test asserting that a coordinator from chapter A cannot retrieve peer mentors from chapter B. Display each mentor's chapter affiliation in the list row so coordinators can visually verify scope.

Contingency: If RLS scope is found to be too permissive in testing, apply a server-side coordinator_id filter as a secondary guard on the query. Block the feature release until the scope test passes consistently.