critical priority low complexity infrastructure pending backend specialist Tier 0

Acceptance Criteria

SearchDebounceUtility class is created at lib/core/search/search_debounce_utility.dart
Class accepts a Stream<String> input and exposes a Stream<String> debouncedOutput getter
Default debounce delay is 300ms; class is instantiable without providing a custom delay
Queries shorter than 2 characters are filtered out and do not appear on debouncedOutput
Emitting multiple values within the debounce window results in only the last value being emitted on debouncedOutput after the window expires
Emitting a new value cancels any pending timer/debounce for the previous value
dispose() method cancels all active subscriptions and timers without throwing
After dispose(), pushing a new value to the input stream does not emit on debouncedOutput
Class has no dependency on Flutter framework (pure Dart) — can be tested without Flutter widget tree
Unit tests in test/core/search/search_debounce_utility_test.dart all pass

Technical Requirements

frameworks
rxdart (debounceTime operator) or dart:async Timer
flutter_test / fake_async for timer-based tests
apis
dart:async StreamController
rxdart BehaviorSubject or PublishSubject (optional)
performance requirements
Debounce timer must fire within ±10ms of configured delay under normal load
Memory footprint must be zero after dispose() — no retained stream subscriptions
security requirements
Input values must not be logged or persisted by this utility — it is a pure transformation layer

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Prefer rxdart's debounceTime operator over a raw Timer — it handles cancellation, stream lifecycle, and backpressure correctly out of the box. The class should use a StreamController internally and pipe the filtered+debounced values to a public stream. If rxdart is already in pubspec.yaml use it; if not, evaluate adding it as this utility will be reused across multiple search features. Minimum query length filtering should happen BEFORE debouncing (filter then debounce) to avoid firing a debounce timer for a keystroke that would be filtered anyway.

Expose the class as final — it should not be extended. The dispose() method should call close() on internal StreamControllers and cancel subscriptions; document that callers are responsible for calling dispose() when the utility is no longer needed (typically in a BLoC or Riverpod notifier's dispose/close).

Testing Requirements

Unit tests using flutter_test with fake_async to control timer advancement without real wall-clock delays. Test cases: (1) single value emitted after delay, (2) rapid successive values — only last emitted, (3) value below min length filtered, (4) value at exact min length passes through, (5) dispose prevents further emissions. Use fake_async.elapse() to advance time precisely. No real async timers — all time-dependent behaviour tested with fake_async.

Component
Search Debounce Utility
infrastructure 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.