Teaching search to wait before it says no

LunarCrush · Production shipped for the false-negative states and the duplicate low-audience creator result; Bellichek remains a separate data decision

A LunarCrush search fix that turned one bad empty state into three explicit states: still looking, possible correction, and real no-match.

A small search bug exposed a larger product problem. Joe had typed coach bellichek on mobile and the product answered too early: zero results, no matches found, then a few seconds later a real answer appeared. The interface was not only wrong. It was breaking trust at the exact moment the user was trying to find out whether LunarCrush had a topic or not.

The fix was not to make the empty state prettier. The fix was to make the product more honest about what it knew.

The bug was a timing problem, not just a copy problem

The first ticket, DEV-12845, captured the visible failure: the search panel declared zero results while the remote lookup was still in flight. That meant the product was presenting a temporary client-side state as a final answer. The second ticket, DEV-12846, showed a related failure while typing. coach b could be interpreted as b, which produced hundreds of unrelated prefix matches before the intended query had time to settle.

Those are different bugs, but the design problem is the same: search was treating uncertainty as if it were knowledge.

Designing the states before shipping the fix

I turned the problem into three explicit states in Paper:

  1. Searching. The product is still looking across every topic it tracks. It should not say that nothing matched yet.
  2. Did you mean. The product found a likely correction and should make the suggestion legible instead of pretending the query is clean.
  3. Nothing matched. The product has finished checking and can now explain that it does not have a match.

The search state keeps the query visible and says that LunarCrush is still checking every tracked topic. It avoids the premature zero-result claim.

The distinction matters because a search box is not only navigation. In a product like LunarCrush, it is also a claim about the data universe. When it says no, it is telling a customer what LunarCrush does not cover. That answer needs a higher standard than a local list finishing before a remote request.

The UI needed to serve engineering, QA and product review

The Paper board was organized as a small state machine instead of a static comp. Each state had desktop and mobile variants, plus notes that made the expected behavior testable. That made the ticket easier to verify because QA could ask a concrete question: does the interface wait until the remote lookup finishes before showing the final empty state?

The small state notes made the difference testable: one note for a possible correction, one for a true no-match.

The implementation then shipped to staging with two changes: DEV-12845 stopped the panel from claiming no results while lookup was still running, and DEV-12846 stopped mid-typing from treating coach b as only b. Both tickets later reached QA Confirmed in Linear, assigned to Jon for the review path.

October 5: production made the boundary sharper

The search fixes reached production on October 5. I promoted the small set deliberately, on top of the live production branch, instead of dragging the whole staging surface with it. The production checks passed, the app deployed, and the original behaviors were verified on lunarcrush.com. Search no longer shows the premature empty state while it is still looking, and coach b no longer collapses into a broad b query while the user is typing.

A second search cleanup shipped the same day for brand queries. facebook had been returning duplicate creator rows, including low-audience rows that looked like bad data. Production now shows a single Facebook creator result, with the same behavior as staging for google, apple, nvidia and meta.

The boundary is clearer now. This article covers search honesty: do not say no too early, do not turn a partial query into the wrong query, and do not show duplicate brand rows that make the data universe look sloppy. The remaining bellichek behavior is a different problem. It depends on the underlying topic and data decision in DEV-12715 and DEV-12741, not on the loading state documented here.

What is still unresolved

The first false-negative fixes and the brand duplicate cleanup are production work now. It fixes the false negative and the mid-typing query collapse, but it does not answer every long-tail search question. If the product wants to support open-ended phrases like meta muse hack cybercrime, that becomes a different problem: semantic search, topic creation, or a separate exploratory flow. This update only documents the boundary I worked on here: do not say no until the product has actually finished looking.

Rebuilding LunarCrush

Nicolás Bañados