Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

06 — Screen Specifications (Search Module)

The authoritative behavioral spec for the Search results surface. Every backend behavior below is verified against source with file:line references. Client behaviors are the forward-looking spec (PRD keeps mobile out of Phase 1 — 00-shared/12 A1). This is the largest file in the package by design: it pins the full state machine of the only full screen plus the in-shell bar.


1. Global Search Bar (in-shell)

1.1 Composition

[AppBar leading]  SearchBarField  [scan icon*]  [clear ✕]
                  (hint, debounce, submit)
* forward-looking
  • Height 48 dp; rounded 24 dp; AppTextField variant with search icon (shared 00-shared/03).
  • Clear button appears only when text is non-empty; clears and re-shows landing (zero-state, 05 §4).

1.2 Behavior contract

EventClient actionServer effect
Text change (composing, non-empty)restart 300 ms debounce timer (proposed); fire request on expiryGET /search?q=… — trimmed query; blank/whitespace q short-circuits server-side to empty result (search.service.ts:28-30)
Text change (empty)cancel timer; emit idle; show landingno request
Submit (enter / search action)commit query immediately, cancel debouncesame endpoint; page resets to 1
Tap "See all X"set entityType param, page 1server filters fetched page by type (search.service.ts:44-49)
Tap resultnavigate to owner module detail (/students/:id etc.)none — result carries entityType + entityId (search.service.ts:11-12)

1.3 Debounce specification

  • Delay: 300 ms (proposed) — tuned for a Mongo $text query with skip/limit (search-index.repository.ts:27-33).
  • Rule: only the latest query fires; any earlier in-flight response for a superseded query is discarded (stale-guard token, 13 §3).
  • Exceptions: no debounce on submit; no debounce on pagination (fires immediately).
  • Why: each keystroke would otherwise hit $text + countDocuments + optional fallback — two queries per search (search.service.ts:36-37).

1.4 Permission behavior

  • Bar hidden entirely without search permission (permissions.constants.ts:93).
  • If the token expires mid-typing: 401 → global session-expiry flow (00-shared/10 §3), state preserved, re-login resumes.

2. Results Screen State Machine

States (legend per 05): idle → loading → success | error | empty; the machine is implemented as SearchCubit (13).

2.1 idle

  • No query. Landing content only (05 §4). No network.

2.2 loading

  • Triggered: debounce expiry, submit, pagination fetch, retry.
  • UI: previous results stay visible for refinement (reload, not replace); first-ever query shows SearchResultSkeleton (07 §4).
  • Inline spinner in bar; list dimmed at 40% opacity.
  • Minimum spinner display 250 ms to avoid flash on fast responses (proposed).

2.3 success

  • data: SearchResult[] (search.service.ts:10-16), rendered grouped.
  • Grouping: client groups by entityType; server sends flat list (search.service.ts:51-57). Group order (proposed): Student, User, Teacher, Staff, Parent, Book, Organization, Lead, Announcement — stable, types absent from results are omitted. Header shows label + per-page count.
  • Row anatomy (SearchResultTile):
    • Leading: AppAvatar (initials from title; entity-type icon fallback).
    • Title: title (e.g. full name, admission number, book title — extraction rules search-indexer.service.ts:68-75).
    • Subtitle: description (email, book description, announcement body — :77-81), ellipsized 2 lines.
    • Trailing: AppTags for tags (role/department/grade — :83-86), max 2 + "+n".
    • A11y label: "{title}, {entityType}, {description}".
  • Empty description/tags: omit rows' subtitle/trailing — defaults are '' and [] (search-index.schema.ts:18-22).
  • Pagination: scroll to 80% of the list → fetch page+1 (limit 20 default, max 50 — search-query.dto.ts:23-29) if meta.hasNext (pagination-query.dto.ts:41-55). Append; show bottom loading row.
  • Counts: header count is per fetched page, not a global total — server computes totalItems from countSearch(q) (search.service.ts:37), but per-type totals are unknowable from the flat response (search.service.ts:44-49).

2.4 empty

  • Server: data: [] with meta.totalItems: 0. Two sources:
    1. blank/whitespace qsearch.service.ts:28-30 (client never sends it, by contract);
    2. no matches in index (text index or fallback scan) — search-index.repository.ts:20-50.
  • UI: AppEmptyState: "No results for “q”", sub-line "Check the spelling or try fewer words." CTA: "Clear search" (returns to landing).
  • Do not show suggestions (no backend suggestions (forward-looking)).

2.5 error

  • Server auto-fails-over: $text failure → regex fallback (search.service.ts:35-41) — so most query-level failures never reach the client. Client error triggers on: 401 (session), 403 (permission revoked mid-session), 429 (rate limited — 00-shared/07 §4), network/offline.
  • UI: AppErrorState with message + Retry (re-run same query) and "Back".
  • Offline: AppOfflineBanner + cached-last-results if any; retry on connectivity restore (00-shared/10 §2).

2.6 permission

  • Not reached from the bar (hidden), but direct deep links (forward-looking) must render a permission state: icon + "You don't have search access." (403PERMISSION_DENIED, 00-shared/07 §3).

3. Query contract (what the client sends)

ParamSourceRules
qsearch-query.dto.ts:6-9optional string; client trims; never send blank; recommended client cap 100 chars (proposed) — server has no max
entityTypesearch-query.dto.ts:11-14optional string; client sends one of the 9 known types
pagesearch-query.dto.ts:16-21≥1, default 1
limitsearch-query.dto.ts:23-291–50, default 20; validation errors → 400 VALIDATION_ERROR (00-shared/07 §3)

4. Empty-query behavior (exact)

Server: !q?.trim(){ data: [], meta: buildPaginationMeta(page, limit, 0) } (search.service.ts:28-30) — totalPages computes to 1, hasNext/hasPrevious false (pagination-query.dto.ts:46-54). No index access. Client contract: never send it; landing screen (05 §4) covers the idle state.

5. Stale-data posture

  • Index freshness = event-driven only; no reindex job in code (search-indexer.service.ts:44-46).
  • Known staleness: Lead and Announcement rows never update/delete (search-indexer.service.ts:29-31) → renamed/edited leads and edited announcements surface stale titles until (planned) reindex/event wiring.
  • UI: no staleness signal today; when a reindex job ships (planned), surface updatedAt on tiles (base.schema.ts:31-33).

6. Keyboard / platform adaptations

  • Tablet/desktop: two-column group layout; / focuses bar; Esc clears.
  • IME composing ignored until composition end (CJK-safe debounce).
  • Landscape: same as portrait, more tiles per row.