13 — State Management (Search Module)
- 1. Cubits
- 2. Repository
- 3. Debounce + stale-guard flow (exact)
- 4. Entity-type filter state
- 5. Failure & retry
- 6. What is NOT in state
Client state architecture for global search. Conventions from 00-shared/06_State_Management.md (flutter_bloc,
get_itDI, repository layer withdio, no server-state libs). Search is a pure server-state surface — no local persistence(forward-looking)history aside.
1. Cubits
1.1 SearchCubit (results screen + in-shell bar share one instance)
| State | Fields |
|---|---|
SearchInitial | — |
SearchLoading | previous SearchResults? (kept for dimmed view) |
SearchSuccess | SearchResults (query, entityType, list, PaginationMeta) |
SearchEmpty | query (raw, for copy) |
SearchError | ApiException(code, status, message), query |
SearchResults model mirrors SearchResult[] + PaginationMeta
(search.service.ts:10-16, pagination-query.dto.ts:32-39).
1.2 SearchHistoryCubit (forward-looking)
Device-storage recent queries (ledger B5 — server has no history endpoint).
2. Repository
SearchRepository (single method):
Future<SearchPage> search({
required String q, String? entityType, int page = 1, int limit = 20,
}) async => /* GET /search, maps envelope → SearchPage */;
- Only layer touching HTTP (
00-shared/06 §2); throws typedApiException(401/403/429/5xx —12 §5). - No caching in this layer today — matches backend (Redis unwired,
redis-cache.service.ts:16-19); add(proposed)TTL cache keyedsearch:{q}:{type}:{page}if latency budgets demand (00-shared/10 §1).
3. Debounce + stale-guard flow (exact)
sequenceDiagram
participant U as User
participant B as SearchBarField
participant C as SearchCubit
participant R as SearchRepository
participant S as API (GET /search)
U->>B: type "rah"
B->>B: restart 300ms Timer
U->>B: type "rahul sharma"
B->>B: restart 300ms Timer (cancel prev)
B->>C: onQueryChanged("rahul sharma")
C->>C: guardToken++; emit Loading(prevResults)
C->>R: search(q, page=1)
R->>S: GET /search?q=rahul+sharma
S-->>R: 200 {data, meta}
R-->>C: SearchPage
C->>C: token fresh? yes → emit Success(results)
Note over C: stale responses (token mismatch) discarded
U->>B: type "rahul sharm"
B->>C: onQueryChanged (timer)
Note over C,R,S: same flow; old in-flight response now stale → dropped
Rules (06 §1.3, 10 §2):
- Timer restarts on every keystroke; fires only after 300 ms of silence.
- Submit (
textInputAction.search) skips the timer. - Guard token: each new query invalidates earlier in-flight responses — prevents out-of-order rendering.
- Pagination:
loadMore()guarded bymeta.hasNext(pagination-query.dto.ts:52) and!isLoadingPage; appends, never replaces.
4. Entity-type filter state
entityTypelives in the cubit, not in route params (keeps back-navigation cheap,10 §3); route params optional(forward-looking)for deep links.- See-all sets
entityType+ page 1; clearing restores the aggregate. - Back from detail: cubit survives (app-shell scope), so results are intact.
5. Failure & retry
SearchError→AppErrorStateretry re-emits the same query (token++, page 1).- 401 → global session-expiry flow (
00-shared/10 §3), query preserved. - Offline → banner + last
SearchSuccessretained (10 §7).
6. What is NOT in state
- No per-type totals (server cannot provide them beyond the fetched page —
search.service.ts:44-49). - No debounce state (owned by the widget's
Timer). - No history/suggestions (
(forward-looking)). - No Redis/persistent cache (
(proposed)).