03 — User Journey (Search Module)
- J1. Find a student by admission number (receptionist)
- J2. Locate a staff member by email
- J3. Search a book in the catalogue (librarian)
- J4. Drill into a type ("see all")
- J5. Zero results
- J6. Search during degraded backend (fallback path)
- J7. Re-search after entity change
- J8. (Forward-looking) QR scan → search
- J9. (Forward-looking) Push deep link → search
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)
- Receptionist opens the app → global search bar visible in the app shell (entry point per 05_Screen_Inventory.md §1).
- Types
ADM-20— client debounces ~300 ms(proposed). GET /api/v1/search?q=ADM-20&page=1&limit=20fires (search.controller.ts:15-18); server matchestext/titlecontaining the token — admission numbers land intitleviaadmissionNumberfallback (search-indexer.service.ts:71).Studentgroup appears first with matching rows (entityTypeon each result —search.service.ts:51-57); receptionist taps the row.- 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
- User types a partial email, e.g.
sharma@. - Regex fallback path may be hit — emails are indexed as
description(search-indexer.service.ts:77-79);$textand fallback both coverdescription. Staffrows show; result tile renders title (name) + description (email).
J3. Search a book in the catalogue (librarian)
- Types a partial title.
$textmatches word stems; short fragments fall back to the case-insensitive substring scan. Bookgroup shown with title + description; tap → library book detail.
J4. Drill into a type ("see all")
- User runs
q=qand sees 40 mixed results (2 pages of 20 —limitmax 50, default 20,search-query.dto.ts:23-29). - 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, sototalItemsreflects the filtered page length, not the true per-type total (search.service.ts:46-48) — flagged in QA-14. - Paging continues through the type-filtered list.
J5. Zero results
- Types gibberish or a query with no indexed match →
data: [],meta.totalItems: 0(search.service.ts:28-30is the empty-query path; no-match path returns empty viarepo.search). - Client shows
AppEmptyState("No results for “x”") with a clear-query CTA (shared 00-shared/03).
J6. Search during degraded backend (fallback path)
- Mongo
$textthrows (e.g. malformed index state) →SearchServicecatches and re-runs the regex fallback transparently (search.service.ts:35-41). - User sees identical UI, possibly different result order (fallback has no
relevance sort —
search-index.repository.ts:49).
J7. Re-search after entity change
- A student is renamed →
StudentUpdatedevent → indexer upserts the row (search-indexer.service.ts:48-96) → next search reflects the new title. - 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).
J8. (Forward-looking) QR scan → search
- User taps the scan icon in the search bar; scans a printed admission number / book barcode.
- Camera value fills
q→ normal search flow. No QR backend endpoints exist (ledger B4) — fully client-side(forward-looking).
J9. (Forward-looking) Push deep link → search
- A notification arrives for a new announcement
(forward-looking)— push device registry not implemented (ledger B3). - Tap → app opens with
?q=<title>prefilled → standard results screen.