high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

MapFilterPanel renders as a DraggableScrollableSheet with collapsed height of 80dp and expanded height covering 50% of screen
Drag handle widget has Semantics label 'Expand filter panel' when collapsed and 'Collapse filter panel' when expanded
Availability status filter renders as a segmented control or chip group with one chip per status value (available, busy, paused, all); each chip has an explicit Semantics label
Specialisation filter renders as a multi-select chip list populated from the BLoC state (dynamic values, not hardcoded)
All form controls are keyboard-navigable via Tab/Shift-Tab and respond to Enter/Space to toggle selection
Any change to filter values emits a FilterChanged(MapFilterState) event to the map screen BLoC immediately (no confirm button required)
FilterChanged event is debounced by 300ms to prevent excessive BLoC emissions during rapid chip toggling
Panel expand/collapse animation completes in ≀300ms with a Material curves.easeInOut curve
Applying filters does not trigger a new Supabase query β€” filtering is applied client-side to the already-loaded BLoC state
Filter state survives view toggling (map ↔ list) β€” verified by parent BLoC holding FilterState independently of panel open/close
Resetting all filters returns to the default state (all statuses, no specialisation filter) and emits a FilterReset event
Panel is dismissible by dragging down or tapping outside, with VoiceOver/TalkBack focus returning to the map canvas on dismiss

Technical Requirements

frameworks
Flutter
BLoC
data models
assignment
contact
performance requirements
Client-side filter application must complete in under 50ms for up to 500 mentor records
DraggableScrollableSheet animation must not drop below 60fps on mid-range Android devices
security requirements
No PII transmitted or stored by the filter panel β€” filter values are availability enums and specialisation IDs only
ui components
DraggableScrollableSheet (Flutter)
Semantics-wrapped drag handle
FilterChip widgets for availability status and specialisation
Wrap layout for chip overflow handling
Design token typography and spacing

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use DraggableScrollableSheet as the container β€” it provides the drag gesture and sheet sizing natively. Store expand/collapse state in the parent BLoC (not local widget state) so the sheet position survives navigation stack changes. Implement debounce using either RxDart's debounceTime or a manual Timer in the BLoC event handler β€” prefer the BLoC layer so the widget remains stateless. For the specialisation filter, load available specialisations from the same BLoC state that loaded the mentor list (avoid a separate API call).

Each FilterChip must have a Semantics label that includes both the chip text and its selected state (e.g., 'Available, selected' vs 'Available, not selected') β€” use the selected property of Semantics to convey this. Do not use showModalBottomSheet β€” build the panel as a persistent child of the MapViewScreen scaffold so it overlaps the map without replacing the route.

Testing Requirements

Unit tests: verify FilterChanged event emitted with correct FilterState on each chip toggle; verify debounce prevents multiple events within 300ms window; verify FilterReset emits correct default state. Widget tests: golden test for collapsed and expanded states; verify all Semantics labels on chips and drag handle; verify keyboard navigation traversal order via SemanticsController. Integration tests: toggle filters and verify BLoC state reflects changes; toggle view (map↔list) and assert FilterState is unchanged. Coverage target: 90%+ on filter logic, 100% on event emission paths.

Component
Map Filter Panel
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.