Appearance
Shop listing flow
The listing page has two display states — all items and filtered items — and two independent ways to move between them: search and filters. Both states come from the same endpoints; what changes is the body and query you send.
Stages
Same thing as a flow, if that reads better for the spec:
API calls by stage
| Stage | Action | API call | Auth |
|---|---|---|---|
| 1 | All items | POST /api/v1/mobile/tags-catalog — payload with the buckets to fetch; limit/offset, sortItem + sortType as query params. Guest mount: /mobile/open/tags-catalog | Customer or Brand |
| 1 | Build the filter UI's options | GET /api/v1/mobile/master-data/brand/category-brandname — "the facet values (categories + brand names) available for the token's brand"; GET /api/v1/mobile/master-data for the wider set | Brand |
| 2 | Search | POST /api/v1/brand/mobile/search?query=… — Typesense. The term is a query param, not part of the body; options.page / options.per_page paginate | Brand |
| 2 | Search, the other way | search is also a query param on tags-catalog and filter-catalog. Two different engines — pick one per screen, deliberately | Customer or Brand |
| 2 | Apply filter | POST /api/v1/mobile/filter-catalog — payload is an array of { filterColumn, filterValue }; or the facet_* keys on the Typesense search's filters | Brand |
| 1 ↔ 2 | Cancel search / Clear filter | No dedicated call — re-issue the stage 1 request without the search param or the filter body | Customer or Brand |
| → detail | Tap a card | GET /api/v1/mobile/getproduct/:slug — see the purchase flow | Brand |
Live stock depends on the token, not the request
with_inventory defaults to true on the Typesense search, and with_inventory: true plus a hubId + roomId on the token routes the search to the hub-product collection (live stock). Without them it hits the global product collection. The same hubId / roomId pair is accepted as a query-param override on the catalogue endpoints, but only when the token doesn't already carry it — so which store's stock a shopper sees is a property of the mobile key they were issued.
Two filter vocabularies
The Typesense filters object accepts a legacy shape (category, subCategory, brandName, tags) and a newer facet_* shape (facet_categories, facet_sub_categories, …). They normalise to the same thing server-side; the API docs say to prefer facet_* for new work. negate_filters takes the same keys to exclude rather than include.
Paths
Search Shop Listing Page → all items → search → filtered items
Cancel search Shop Listing Page → filtered items → cancel search → all items
Apply filter Shop Listing Page → all items → apply filter → filtered items
Clear filter Shop Listing Page → filtered items → clear filter → all items
Open questions
Search and filter are drawn here as producing the same "filtered items" state, but they're separate mechanisms. What happens when both are active? Three cases worth pinning down:
- Search while a filter is applied — does search run within the filtered set, or reset the filter?
- "Cancel search" while a filter is applied — back to all items, or back to filtered-only?
- "Clear filter" while a search is active — same question in reverse.
If they're meant to compose, the states are really
all/search only/filter only/both, and each clear action only removes its own layer. Worth deciding before build. The API composes cleanly either way: on the Typesense search,queryandfiltersare independent inputs to one call, so "both active" is a single request — as long as the client keeps the two pieces of state separately and always sends the full picture.Zero results: no path defined for either search or filter. Both endpoints answer with the normal
{ success: true, result: … }envelope and an empty set, so it is a UI state, not an error branch.Where does the filter live — a bottom sheet, a side panel, a separate screen? "Apply filter" implies a surface with its own open/cancel actions that isn't in the flow yet.
Are filters persisted when leaving and returning to the listing page? Nothing server-side holds them; that is client state.
No tap-into-item path yet — presumably listing → item detail. Now captured in the purchase flow.
tags-catalogreturns a keyed object of tag buckets unlessunified_res=trueflattens it to one intersected list. Which shape the listing page wants decides whether the screen is one list or several rails.