critical priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

SearchScreen renders AccessibleSearchInputField at the top and SearchResultsList below in a Column or CustomScrollView
Input changes are debounced by 300 ms before triggering a ContactSearchService query
While a query is in-flight, SearchResultsList receives a loading state
On successful query, SearchResultsList receives a loaded state with the result list
On empty result, SearchResultsList receives an empty state with the query string
On network error, SearchResultsList receives an error state and an accessible error message is shown
Clearing the search field resets the list to its initial (pre-search) state without triggering a query
State is managed via a Riverpod StateNotifierProvider or BLoC — no setState calls for search state
Screen correctly disposes the debounce stream/timer on widget disposal to prevent memory leaks
SearchScreen is navigable from the Contacts tab in the existing bottom navigation without altering the tab's state preservation

Technical Requirements

frameworks
Flutter
Riverpod or BLoC
flutter_test
apis
ContactSearchService
search-debounce-utility
data models
Contact
PeerMentor
performance requirements
Debounce delay must be exactly 300 ms (configurable via a constant)
No more than one concurrent Supabase query per debounce cycle; previous in-flight query must be cancelled before issuing a new one
security requirements
Search query string must be sanitized before passing to ContactSearchService to prevent injection into Supabase query parameters
Results must respect role-based access control — coordinator sees only their assigned contacts; peer mentor sees only their own profile
ui components
SearchScreen
AccessibleSearchInputField
SearchResultsList

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use a single SearchQuery sealed class for state (idle, loading, loaded, empty, error) and drive SearchResultsList from it — avoid multiple booleans like isLoading + hasError. If using Riverpod, prefer an AsyncNotifier with a ref.debounce extension or manual Timer; if BLoC, use a debounce transformer on the SearchQueryChanged event. Cancel the previous Supabase query by storing the Future and using a generation counter pattern (increment on each new query, discard results if generation does not match). Keep SearchScreen focused on wiring only — no business logic or Supabase calls directly in the widget.

Route the screen using the existing go_router or navigation setup; do not push a new MaterialPageRoute outside the router.

Testing Requirements

Unit tests for the Riverpod notifier or BLoC: verify state transitions (idle → loading → loaded, idle → loading → empty, idle → loading → error). Verify debounce cancels previous timers. Widget test: pump SearchScreen with mocked ContactSearchService, type a query, await debounce, assert SearchResultsList receives loaded state. Test clear-field resets to idle.

Integration test: full screen with real provider tree against Supabase staging, verify results render for a known test contact.

Component
Contact Search Screen
ui low
Epic Risks (2)
medium impact medium prob technical

Flutter's Semantics live region support for announcing dynamic result count changes may behave inconsistently between VoiceOver (iOS) and TalkBack (Android), particularly regarding announcement throttling and focus management, causing the feature to pass testing on one platform and fail on the other.

Mitigation & Contingency

Mitigation: Test live region announcements on both iOS (VoiceOver) and Android (TalkBack) early in development using the existing accessibility test harness. Reference the existing LiveRegionAnnouncer component (608-live-region-announcer) patterns used elsewhere in the app.

Contingency: If cross-platform consistency cannot be achieved, implement a platform-specific announcement strategy using the SemanticsService.announce API with platform-conditional announcement timing to work around OS-specific throttling behaviour.

low impact low prob dependency

Voice-to-text progressive enhancement for Blindeforbundet may not be available or may behave unpredictably on all device/OS combinations, particularly older Android devices, potentially causing crashes or silent failures that degrade the search experience.

Mitigation & Contingency

Mitigation: Implement voice-to-text as a strictly optional enhancement: detect availability at runtime, show the microphone button only when the platform speech API reports availability, and wrap all voice invocations in try/catch with graceful degradation to standard text input.

Contingency: If voice-to-text causes instability on a subset of devices discovered during TestFlight/beta, disable the feature flag for that platform version while a fix is investigated, without impacting the core text-based search functionality.