high priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

Given results from both Supabase and Drift containing overlapping contact IDs, when mergeAndDeduplicate() is called, then only one entry per unique contact ID is returned
Given results from multiple sources, when merged, then Supabase results take precedence over Drift results for the same contact ID (online source is authoritative)
Given a merged result set, when sorted, then contacts are ordered consistently by a defined sort key (e.g., display name ascending, then ID as tiebreaker)
Given a merged result set exceeding the configured maximum size (e.g., 50), when truncated, then the returned list contains exactly maxResults items
Given an empty list from both sources, when merged, then an empty list is returned without error
Given results only from Drift (offline), when merged, then those results are returned correctly without requiring Supabase results
Given results only from Supabase (no offline cache), when merged, then those results are returned correctly
Given a configurable maxResults value of 0 or negative, when called, then an ArgumentError is thrown with a descriptive message
The deduplication key must be the contact's unique database ID (not name or phone), ensuring no false deduplication of different people with the same name

Technical Requirements

frameworks
Flutter
Dart
data models
Contact
SearchResult
performance requirements
Deduplication must complete in O(n) time using a Map/Set-based lookup, not O(n²) nested iteration
Merging 200 contacts from two sources must complete in under 10ms on a mid-range device
No unnecessary object allocations — reuse existing contact instances from the authoritative source
security requirements
Do not log individual contact IDs or names during merge operations — contact data is sensitive personal information
Ensure merged results do not expose fields that are role-restricted (e.g., encrypted assignment details visible only to Blindeforbundet coordinators)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement as a pure function or static method `List mergeAndDeduplicate(List supabaseResults, List driftResults, {int maxResults = 50})`. Use a `LinkedHashMap` keyed on contact ID: first insert all Drift results, then overwrite with Supabase results so online data wins. Convert map values to a list, sort by `(a, b) => a.displayName.compareTo(b.displayName)` with ID as tiebreaker, then apply `take(maxResults)`. Keep this function free of side effects — no async, no state mutation — so it is trivially testable.

The maxResults default of 50 should be a named constant in a SearchConstants class to allow future configuration. Do not use List.toSet() for deduplication as it relies on object equality which may not be defined on Contact model.

Testing Requirements

Unit tests only. Test mergeAndDeduplicate() in isolation with mock contact lists. Cover: (1) full overlap — all IDs duplicated, (2) partial overlap — some shared IDs, (3) no overlap — disjoint sets, (4) empty left source, (5) empty right source, (6) both empty, (7) result truncation at maxResults boundary, (8) sort order correctness with identical names but different IDs, (9) Supabase precedence over Drift for same ID. Aim for 100% branch coverage on the merge function.

Use flutter_test package.

Component
Contact Search Service
service low
Epic Risks (2)
medium impact medium prob technical

Cancelling in-flight Supabase HTTP requests via RxDart switchMap may not actually abort the server-side query if the Supabase Dart client does not support request cancellation tokens, leading to wasted API calls and potential race conditions where a slow earlier response arrives after a faster later one.

Mitigation & Contingency

Mitigation: Audit the supabase-flutter client's cancellation support before implementation. Use RxDart switchMap to discard stale emissions even if HTTP cancellation is unavailable, ensuring only the latest result reaches the UI.

Contingency: If race conditions surface in testing, add a query sequence counter to tag each emission and discard any response whose sequence number is lower than the most recently emitted one.

medium impact low prob technical

Connectivity detection used to route between online and offline repositories may have latency or give false positives on flaky connections, causing the service to attempt Supabase queries that time out instead of falling back to the cache promptly.

Mitigation & Contingency

Mitigation: Use a try/catch with a short timeout on Supabase calls. On network error, immediately fall back to the offline repository and emit a cached result with an offline indicator rather than surfacing an error state.

Contingency: If the timeout-based fallback proves insufficient, implement a connection health check stream that pre-validates connectivity before each query batch.