04 — Information Architecture (Search Module)
- 1. Entry point: the global search bar
- 2. What the index contains (searchable universe)
- 3. Result organization
- 4. Deep links
- 5. What is NOT in this IA
- 6. Navigation summary
Where global search lives in the app, what it searches, and how results are organized. Backend facts from
src/modules/search/**; app-shell conventions from 00-shared/05_Global_Information_Architecture.md.
1. Entry point: the global search bar
Search is a cross-module utility, not a tab. It lives as a persistent
search bar in the app shell (top app bar), reachable from every authenticated
screen — mirroring the backend's single cross-cutting endpoint (GET /search,
search.controller.ts:11-18). Route: /search (results surface); the input
itself is an in-shell control that routes to /search on commit.
2. What the index contains (searchable universe)
One denormalized search_indexes collection per tenant
(search-index.schema.ts:7); rows are created from domain events
(search-indexer.service.ts:7-32):
- 7 fully-synced types:
User,Student,Teacher,Staff,Parent,Book,Organization(create/update/delete). - 2 partially-synced types:
Lead(create only),Announcement(create/publish only) — no update/delete events (search-indexer.service.ts:29-31). (planned): batches, sessions, test_series, DPP (docs/IMPLEMENTATION_PLAN.md:771).
3. Result organization
The API returns a flat, relevance-ranked list — there is no server-side
grouping (search.service.ts:10-16, :51-57). The client groups by
entityType with headers (see 06 §4). Filtering by type = the optional
entityType query param (search-query.dto.ts:11-14).
/search
├── Group: Students (entityType=Student)
│ └── SearchResultTile × n (title, description, tags)
├── Group: Books
│ └── ...
└── Group: Announcements
└── ...
Group order is client-proposed (people first, then content) — the server does
not order groups; ordering by textScore happens inside each flat list
(search-index.repository.ts:28-33).
4. Deep links
| Route | Purpose | Backing |
|---|---|---|
/search | results surface for current query | GET /search?q=… |
/search?type=Student | see-all drill-down | entityType param (search-query.dto.ts:11-14) |
/search/result/<entityType>/<entityId> (proposed) | deep-link to a specific result | key = (entityType, entityId) (search.service.ts:11-12) |
Result rows deep-link into the owning module's detail route (Students →
/students/:id, etc.) — no dedicated result-detail screen exists; the
module only returns pointers (entityId, entityType).
5. What is NOT in this IA
- No search history / recent searches / saved searches / suggestions —
(forward-looking), nothing server-side. - No per-entity search endpoints — other modules' lists use their own
qfilters (pagination-query.dto.ts:26-29); this module is the global aggregator only. - No cross-tenant traversal — every row and query carries
tenantId(base.schema.ts:9-31;search-index.repository.ts:84-89). - No results cache in the data path — Redis is tenant-namespaced and
available (
redis-cache.service.ts:16-19) but unused bySearchService(proposed).
6. Navigation summary
| From | To | Trigger |
|---|---|---|
| Any shell screen | /search | tap search bar / type |
/search | /students/:id, /books/:id, … | tap result row (owner module route) |
/search | /search?type=X | "See all X" chip |
/search?type=X | /search | clear type filter |