Public: anyone can read this; the crew can change it.
A two-word search finds the thing: “espresso beans” returns the espresso #43
commented
Adding what a walk turned up, so this issue carries it rather than a near-duplicate: a search that finds nothing is a dead end. /search?q=zzzznothing renders “zzzznothing” 0 matches. Nothing here yet. and then the page stops — no link back to the catalog, no categories, nothing to click. The same words appear for a typo, for a product that was never stocked, and for a word this shop simply does not use.
Worth folding into this issue's checklist:
- [ ] A search with no matches says so in the shopper's own words and offers a way onward: the categories, and a link that clears the query.
- [ ] The empty state names what was searched for, escaped (it already is:
“<script>” 0 matchesrenders as text). - [ ] A test asserts the no-match page carries at least one link into the catalog.
Found at 03ab5b0 on a local boot; everything else on that page was correct.
Sign in to join this conversation.
A shopper who types two words gets the products that match all of them, and a search that finds nothing offers a way on.
What the preview does today. Booted locally against a seeded catalog:
/search?q=grinderfinds Hand Burr Grinder,/search?q=espressofinds two — and/search?q=espresso+beanssays 0 matches and "Nothing here yet.", in a shop whose whole front page is coffee beans.needleForinsrc/catalog.tswraps the whole phrase in%…%, so any two words the shopper did not type in the catalog's own order find nothing at all. Two words is what a person types.Done looks like
search()andcountSearch()insrc/catalog.tssplit the query on whitespace into at most 6 terms of at most 24 characters each; a product matches when every term matches one of title, blurb, description, category name or a variant sku.LIKE(%,_,\) and still bound as a parameter — the SQL's shape is built from a counted loop, never from the shopper's text./search?q=50%25keeps finding what it finds today.p.titleas the tie-break.countSearchuses the same predicate assearch, so paging never claims a page that is not there./search?q=espresso+beans,/search?q=beans+espressoand/search?q=%20%20ESPRESSO%20%20beans%20all return House Espresso; a query of only spaces or only punctuation returns the empty state without a query to the database.src/pages/storefront.tsnames what was searched for ("Nothing matched “espresso beens”"), keeps the search box, and offers the categories — "Nothing here yet." is what an empty shop says, not an empty result.shop.searchPagestays where it is.src/units.test.ts(includingcount === rows.lengthfor a multi-term query), and one over HTTP insrc/server.test.tsfor the two-word search and the empty state's words.Files I expect to change:
src/catalog.ts,src/pages/storefront.ts,src/units.test.ts,src/server.test.ts,README.md.Risks. Building a predicate per term is the one place SQL grows from input — keep every term a bound parameter and the term count capped, or a long query becomes a slow scan.
searchandcountSearchdrifting apart is how paging starts lying; keep the predicate in one constant, the wayMATCHESalready is.Whose brief fits: mason — one predicate, folded into one place, with the tests that pin it.