05 — Screen Inventory (Search Module)
- Legend
- 1. Global Search Bar (in-shell control, every authenticated screen)
- 2. Search Results Screen (
/search) — the module's only full screen - 3. See-All Drill-Down (
/search?type=<entityType>) - 4. Zero-State / Landing (
/searchwith empty query) - 5. Recent Searches panel
(forward-looking) - 6. Scan-to-Search sheet
(forward-looking) - Shared components used
- Analytics events (proposed)
- Keyboard, landscape, tablet, desktop
Every screen of the Search module, its intent, route, composition, states, permissions, platform behavior and events. Authoritative components in 00-shared/03; this file enumerates which ones each screen uses with module specifics. Backend source:
src/modules/search/**.
Legend
States = idle / loading / success / empty / error(offline, server, invalid) / disabled / permission.
Analytics events follow {module}.{screen}.{action} (proposed; SDK open — 00-shared/10 §8).
1. Global Search Bar (in-shell control, every authenticated screen)
| Field | Detail |
|---|---|
| Purpose | Capture the query that drives the single search endpoint |
| Entry | present on all shell screens; focused state → /search |
| Exit | submit → /search?q=…; cancel → back to previous screen |
| Input | AppTextField-based SearchBarField (module component, 07 §1): hint "Search students, books, announcements…", clear button, optional scan icon (forward-looking) |
| Behavior | debounced 300 ms (proposed); results update in place while typing; submit commits exact query |
| Permission | search (permissions.constants.ts:93); without it the bar is hidden |
| States | idle, typing (IME composing), loading (inline spinner, no layout jump), disabled (permission), offline (banner) |
| Keyboard | TextInputType.text, textInputAction.search, autocorrect off (names/IDs) |
| a11y | semantics hint "Search the whole school"; clear button labeled; live-region on result count |
| Analytics | search.bar.focus, search.bar.submit, search.bar.clear (proposed) |
| Motion | focus underline m-fast; results fade/slide m-base (00-shared/08) |
2. Search Results Screen (/search) — the module's only full screen
| Field | Detail |
|---|---|
| Purpose | Show relevance-ranked results grouped by entity type; jump to any entity |
| Entry | from search bar submit; deep link ?q= (forward-looking) |
| Exit | tap result → owner module detail route; back → shell |
| Source | GET /api/v1/search?q&entityType&page&limit — flat SearchResult[] + PaginationMeta (search.service.ts:10-16,59) |
| Groups | client-side grouping by entityType (search.service.ts:11); group headers with counts (count = rows on page, not total — see QA-14) |
| Loading | AppSkeleton list; keep previous results + dim while typing new query (avoid flicker) |
| Empty | AppEmptyState "No results for “q”" + clear CTA (server returns data: [] — search.service.ts:28-30) |
| Error | AppErrorState with retry; note: server auto-falls back to regex internally (search.service.ts:35-41), so client errors are rare |
| Pagination | infinite scroll (page+1 on scroll end); meta.hasNext gate (pagination-query.dto.ts:41-55) |
| Pull-to-refresh | RefreshIndicator re-runs current query (fresh index read) |
| a11y | group headers as headings; per-row semantics label "{title}, {entityType}, {description}" |
| Motion | group insert/remove m-base; row tap ripple |
| Analytics | search.results.shown(count,type), search.result.tap(entityType), search.results.empty, search.seeall.tap(type) (proposed) |
3. See-All Drill-Down (/search?type=<entityType>)
| Field | Detail |
|---|---|
| Purpose | Browse all matches of one entity type with pagination |
| Source | same endpoint, entityType set (search-query.dto.ts:11-14) |
| Grouping | no groups — flat list of one type; app bar shows type label + result count |
| Empty | "No {type} found for “q”" |
| Caveat | server filters the fetched page only (search.service.ts:44-49) — drill-down paging stays correct per page, but cross-page type totals are approximations; QA-14 |
| Exit | back → /search (query preserved in cubit state) |
4. Zero-State / Landing (/search with empty query)
| Field | Detail |
|---|---|
| Purpose | Explain the surface before any query |
| Source | none — client-only; server returns data: [] for blank q (search.service.ts:28-30) |
| Content | icon, "Search across your school", example chips ("Student name", "Admission no.", "Book title", "Announcement") — tap chip fills bar |
| States | only idle + offline |
| Analytics | search.landing.chip.tap(topic) (proposed) |
5. Recent Searches panel (forward-looking)
| Field | Detail |
|---|---|
| Purpose | Quick re-run of past queries (client-side persistence only — ledger B5) |
| Source | none — device storage; no backend history endpoint |
| Note | keep dormant; single-tap re-run; clear-all action |
6. Scan-to-Search sheet (forward-looking)
QR/barcode capture filling q — no backend QR support (ledger B4); see 03 J8.
Shared components used
AppTextField, AppButton, AppChips, AppAvatar, AppListTile,
AppSnackbar, AppSkeleton, AppEmptyState, AppErrorState,
AppOfflineBanner, AppSpinner, AppTag. Module-specific: SearchBarField,
SearchResultTile, SearchGroupHeader, SearchResultSkeleton — defined in
07_Component_Library.md.
Analytics events (proposed)
search.bar.{focus,submit,clear}, search.landing.chip.tap,
search.results.{shown,tap,empty,seeall}, search.error.{server,offline}
(all proposed).
Keyboard, landscape, tablet, desktop
- Portrait: bar top, results scroll below.
- Tablet/desktop: results in two-column grid groups; keyboard shortcuts
/focuses search (desktop), Esc clears. - Keyboard avoidance +
resizeToAvoidBottomInset(shared baseline).