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

03 — User Journey (Search Module)

End-to-end journeys for the global search surface. Every backend behavior cited is implemented; screen-level behaviors are the forward-looking client spec. States per 00-shared/10 §1 conventions.


J1. Find a student by admission number (receptionist)

  1. Receptionist opens the app → global search bar visible in the app shell (entry point per 05_Screen_Inventory.md §1).
  2. Types ADM-20 — client debounces ~300 ms (proposed).
  3. GET /api/v1/search?q=ADM-20&page=1&limit=20 fires (search.controller.ts:15-18); server matches text/title containing the token — admission numbers land in title via admissionNumber fallback (search-indexer.service.ts:71).
  4. Student group appears first with matching rows (entityType on each result — search.service.ts:51-57); receptionist taps the row.
  5. Navigates to the student detail screen (module-owned route).

Backend guarantees: tenant-scoped results (search-index.repository.ts:24-26); relevance order by textScore (:28-33).

J2. Locate a staff member by email

  1. User types a partial email, e.g. sharma@.
  2. Regex fallback path may be hit — emails are indexed as description (search-indexer.service.ts:77-79); $text and fallback both cover description.
  3. Staff rows show; result tile renders title (name) + description (email).

J3. Search a book in the catalogue (librarian)

  1. Types a partial title. $text matches word stems; short fragments fall back to the case-insensitive substring scan.
  2. Book group shown with title + description; tap → library book detail.

J4. Drill into a type ("see all")

  1. User runs q=q and sees 40 mixed results (2 pages of 20 — limit max 50, default 20, search-query.dto.ts:23-29).
  2. Taps "See all Students" → same endpoint with entityType=Student (search-query.dto.ts:11-14) — server filters in memory after the search (search.service.ts:44-49); note: the filter is applied to the current page only, so totalItems reflects the filtered page length, not the true per-type total (search.service.ts:46-48) — flagged in QA-14.
  3. Paging continues through the type-filtered list.

J5. Zero results

  1. Types gibberish or a query with no indexed match → data: [], meta.totalItems: 0 (search.service.ts:28-30 is the empty-query path; no-match path returns empty via repo.search).
  2. Client shows AppEmptyState ("No results for “x”") with a clear-query CTA (shared 00-shared/03).

J6. Search during degraded backend (fallback path)

  1. Mongo $text throws (e.g. malformed index state) → SearchService catches and re-runs the regex fallback transparently (search.service.ts:35-41).
  2. User sees identical UI, possibly different result order (fallback has no relevance sort — search-index.repository.ts:49).

J7. Re-search after entity change

  1. A student is renamed → StudentUpdated event → indexer upserts the row (search-indexer.service.ts:48-96) → next search reflects the new title.
  2. Known gap: renaming a lead or editing an announcement never fires an indexed event (search-indexer.service.ts:29-31) → stale rows until a future reindex job (planned).
  1. User taps the scan icon in the search bar; scans a printed admission number / book barcode.
  2. Camera value fills q → normal search flow. No QR backend endpoints exist (ledger B4) — fully client-side (forward-looking).
  1. A notification arrives for a new announcement (forward-looking) — push device registry not implemented (ledger B3).
  2. Tap → app opens with ?q=<title> prefilled → standard results screen.