high priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

MentorInfoPopup renders within one frame of MarkerSelected event — no network request is made on open
Popup displays: mentor full name, availability status badge, list of specialisations (up to 5, with overflow '+N more'), and a 'View profile' button
Availability status badge uses the same colour token as the corresponding MapMarkerWidget for visual consistency
VoiceOver/TalkBack announces on popup open: 'Mentor profile: {mentorName}, {availabilityStatus}' as the first focusable element
Focus is trapped within the popup while it is open — Tab/Shift-Tab cycle between close button and 'View profile' button only
Close button has Semantics label 'Close mentor profile' and is the last focusable element in the trap cycle
Tapping 'View profile' navigates to the peer mentor detail screen using GoRouter deep link and closes the popup
Popup is dismissible by tapping outside its bounds; on dismiss, focus returns to the map marker that triggered it (or the canvas)
Popup renders correctly on small screens (375dp width) without overflow — specialisation list wraps gracefully
Popup does not make any Supabase query on open — all displayed data comes from the MentorMapData already in BLoC state
Popup slide-in animation from the bottom completes in ≤250ms with easeOut curve
Pressing the system Back button (Android) or swipe-down gesture closes the popup without navigating away from the map screen

Technical Requirements

frameworks
Flutter
BLoC
GoRouter
data models
assignment
contact
performance requirements
Popup must render within one frame (16ms) — all data pre-loaded in BLoC state
Animation must not drop below 60fps on mid-range devices
security requirements
Only non-sensitive mentor data displayed (name, availability, specialisations) — no address, phone, or assignment details
Deep-link to peer mentor detail screen must pass mentorId only — not PII — in the route parameter
No PII stored in popup widget state beyond the current render cycle
ui components
Modal overlay (Stack + AnimatedPositioned or showModalBottomSheet variant)
FocusTrap (Focus widget with FocusTraversalGroup)
Semantics announce-on-open via SemanticsService.announce()
Availability status badge (reusing AvailabilityColorTokens from task-003)
Specialisation chip list with overflow handling
AppButton for 'View profile' deep-link action
Close button with Semantics label

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Do not use showModalBottomSheet for this popup — it pushes a new route onto the Navigator stack, which would require the user to navigate back rather than tap outside to dismiss, and complicates focus management. Instead, render the popup as an AnimatedPositioned child inside the MapViewScreen Stack, toggled by the BLoC's selectedMentorId field. Use SemanticsService.announce(text, TextDirection.ltr) in didUpdateWidget or in a post-frame callback when the popup becomes visible — this is the correct way to trigger a VoiceOver/TalkBack announcement without relying on AutomaticKeepAlive or focus changes. For the focus trap, use a FocusTraversalGroup with OrderedTraversalPolicy containing exactly two FocusNodes: closeButton and viewProfileButton.

The Back button / swipe-down dismiss can be handled with a WillPopScope (Flutter <3.0) or PopScope (Flutter ≥3.0) that emits a PopupDismissed event to the BLoC instead of popping the route. Specialisation overflow: show the first 4 chips and a '+N more' text label for counts above 4 — no expandable section needed in this popup.

Testing Requirements

Unit tests: verify no Supabase or BLoC query is triggered on popup open; verify 'View profile' button constructs the correct GoRouter route with mentorId. Widget tests: golden tests for popup with 1, 3, and 6 specialisations (overflow case); verify Semantics announcement on open via SemanticsController; verify focus trap — simulate Tab key presses and assert focus does not escape the popup; verify close button Semantics label; verify popup dismisses on outside tap and focus returns to expected target. Integration tests: open popup, tap 'View profile', assert navigation to correct peer mentor detail route. Accessibility test: verify announcement text matches expected format.

Coverage target: 100% of popup open/close and navigation logic.

Component
Mentor Info Popup Card
ui low
Epic Risks (3)
high impact high prob technical

Flutter's map canvas (flutter_map) does not natively support semantic focus traversal for screen readers, meaning map markers may be entirely invisible to VoiceOver/TalkBack users. If the accessible list fallback is not treated as a first-class view, screen reader users will have no access to the feature.

Mitigation & Contingency

Mitigation: From sprint 1, treat mentor-list-fallback as a fully featured primary view, not an afterthought. Implement and test it in parallel with the map canvas. Make the view-toggle-button keyboard focusable and announced on every screen state. Conduct VoiceOver testing on device before submitting each PR touching UI components.

Contingency: If map canvas accessibility cannot be achieved for marker focus traversal, make the view-toggle-button the default focus target on screen load for VoiceOver users (detected via screen-reader-detection-service) so they are immediately directed to the list fallback without needing to discover the toggle.

medium impact medium prob technical

The mentor-info-popup must occupy no more than 40% of visible map area on small screens. On devices with screen heights under 667px (iPhone SE), overlapping with the filter panel or obscuring most of the map could severely degrade usability.

Mitigation & Contingency

Mitigation: Implement the popup as a bottom sheet capped at 40% of screen height with a ScrollView for overflow content. Test on iPhone SE (375x667pt) and the smallest commonly used Android form factor in the device lab. Define max-height as a percentage constant in location-privacy-config or design tokens.

Contingency: If the popup cannot fit all required fields within 40% height on smallest targets, truncate assigned contact count and certification badge to icons-only in the compact view, with a 'View Profile' button always visible at the bottom of the popup regardless of scroll position.

medium impact medium prob integration

Filter state must remain perfectly synchronised between the map view and the list fallback. If the filter panel emits state that is not consumed identically by both views, coordinators switching between views will see inconsistent mentor sets, eroding trust in the feature.

Mitigation & Contingency

Mitigation: Store active filter criteria in a single shared Riverpod provider owned by the map-view-screen and consumed by both map-marker-widget (via mentor-location-service) and mentor-list-fallback. Write integration tests that apply a filter, switch views, and assert identical mentor counts in both views.

Contingency: If filter sync proves brittle, simplify to a single filter state object passed explicitly as a constructor argument to both views on each rebuild, eliminating indirect state sharing.