Interaction-level contract for search: state transitions, timing, motion,
input handling. Shared interaction/motion conventions from
00-shared/08_Interaction_&_Motion.md ; this file is module-specific.
State Trigger Visual Exit
idle no query landing (07 §6 ) type → typing
typing keystroke while composing bar spinner off; no network debounce expiry → loading
loading debounce fire / submit / page fetch / retry previous results dimmed; bar spinner; first query → skeleton (07 §4 ) response → success/empty/error
success 200 with data grouped tiles; new groups animate in scroll → loading(page); type → typing; tap → navigate
empty 200 with data: [] AppEmptyState (06 §2.4 )clear → idle; type → typing
error network/401/403/429/5xx AppErrorState (06 §2.5 )retry → loading; back → previous
permission direct deep link without search perm (permissions.constants.ts:93) permission state (06 §2.6 ) n/a
Keystroke → SearchBarField restarts a 300 ms Timer.
Composition end (IME) starts the timer (CJK-safe).
Timer fires → SearchCubit.queryChanged → stale-guard token++ → loading
→ repository call (13 §3).
Any earlier in-flight response whose token is stale is dropped.
Empty input cancels the timer immediately (no request, 08 §1.2 ).
From To Behavior
q=A (results shown)q=Bkeep A results visible while B loads; replace on B success — never blank the list (06 §2.2 )
aggregate view see-all type=X entityType=X set, page 1, list collapses to one group; back restores previous query + type
see-all back restore aggregate view from cubit state (no refetch needed if unchanged)
pagination append page+1 appended; no scroll jump; scroll position preserved
Tap tile → owner-module detail route (Students /students/:id, etc.) —
SearchModule only supplies the pointer (entityId, entityType,
search.service.ts:11-12).
Back from detail → results preserved as-is (cubit state survives).
Deep link /search?q=… (forward-looking): prefill bar, run query.
Interaction Motion
Focus into bar underline grow m-fast (150 ms)
New group appears fade + 4 px rise m-base (250 ms)
Group removal fade out 150 ms, list reflows
Row tap standard ripple; navigation slide m-base
Clear (✕) bar resets to idle m-fast; landing fade-in m-base
Error state shake m-fast on retry failure (match auth module convention)
textInputAction.search submits; Esc (desktop) clears; / (desktop)
focuses bar (06 §6 ).
No IME debounce while composing (08 §1.5 ).
Focus: bar auto-focused on /search entry; cleared on navigation.
Offline → banner + last results retained (if any) + retry on restore
(00-shared/10 §2 ).
Server-side fallback (search.service.ts:35-41) is invisible to the user;
client does not show a "degraded" state (order may differ — fallback has no
textScore sort, search-index.repository.ts:49).
search.bar.focus, search.bar.submit, search.bar.clear,
search.landing.chip.tap, search.results.shown(count),
search.results.empty, search.result.tap(entityType),
search.seeall.tap(type), search.page.next, search.error.{server,offline,retry}.
All (proposed) — SDK open (00-shared/12 A4 ).