critical priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

Each MapMarkerWidget renders a filled circular shape of 32dp diameter with a contrasting border
Marker fill colour maps to mentor availability status via design tokens: available → token.colorAvailable, busy → token.colorBusy, paused → token.colorPaused
All availability status colours meet WCAG 2.2 AA contrast ratio (4.5:1) against the map tile background in both light and dark tile themes
Each marker has a Semantics node with label formatted as '{mentorFirstName} {mentorLastName}, {availabilityStatus}' (e.g. 'Kari Nordmann, available')
Tapping a marker emits a MarkerSelected(mentorId) event to the map screen BLoC
Markers do not use colour as the sole differentiator — each availability state also has a distinct icon or pattern inside the circle
Widget accepts a MentorMapData value object (mentorId, displayName, availabilityStatus, isConsented) and renders only consented mentors
Marker renders correctly at zoom levels 8–17 without pixelation (use Flutter CustomPainter or SVG, not raster assets)
Selected marker (post-tap) shows a visual selected state (e.g. enlarged border) until popup is dismissed
Widget is pure — no direct Supabase or BLoC dependency inside the widget; all data passed via constructor

Technical Requirements

frameworks
Flutter
BLoC
data models
assignment
contact
performance requirements
CustomPainter-based marker must repaint in under 2ms to support 50+ concurrent markers at 60fps
Avoid image assets for markers — use CustomPainter to keep APK size minimal and support dynamic colour tokens
security requirements
Only display markers for mentors where isConsented is explicitly true — never render unconsented mentor locations
Mentor location data precision must be municipality-level only (not exact GPS), enforced by the data layer before this widget receives data
No PII (full name, address) stored in widget state beyond the current render cycle
ui components
CustomPainter circular marker with fill, border, and inner icon
Semantics widget with label and onTap handler
GestureDetector for tap event dispatch
Design token color references (contrast-safe palette)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement marker rendering with CustomPainter rather than Image assets to support dynamic colour tokens and sharp rendering at all zoom levels. Create a MentorMapData value object (immutable, @freezed) to decouple the widget from domain models. The availability colour mapping should live in a dedicated AvailabilityColorTokens class (not inline in the widget) so it can be reused by the list view. Use a distinct inner icon per status (e.g., checkmark for available, clock for busy, pause icon for paused) to avoid colour-only differentiation per accessibility requirements.

The selected state enlargement should use a Transform.scale within an AnimatedContainer — avoid rebuilding the entire widget tree. Wrap the GestureDetector with MergeSemantics to combine the tap action and the descriptive label into a single accessibility node so VoiceOver/TalkBack users get one coherent announcement.

Testing Requirements

Unit tests: verify colour token mapping for all three availability states; verify MarkerSelected event contains correct mentorId on tap; verify isConsented=false markers are not rendered (widget returns SizedBox.shrink or equivalent). Widget tests: golden tests for all three availability states (available, busy, paused); golden test for selected state; verify Semantics label format using SemanticsController. Accessibility tests: verify each marker Semantics node is reachable by traversal order and label is correctly announced. Performance test: render 100 markers simultaneously in widget test and assert no frame drops.

Coverage target: 100% of MapMarkerWidget rendering and event logic.

Component
Mentor Map Marker Widget
ui medium
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.