Skip to content

fix(bestax-mcp): rank the component a search is about above passing mentions - #970

Merged
allxsmith merged 2 commits into
mainfrom
fix/934-search-ranking
Oct 8, 2026
Merged

allxsmith merged 2 commits into
mainfrom
fix/934-search-ranking

Conversation

@allxsmith

Copy link
Copy Markdown
Owner

search_bestax({ query: "date picker" }), the example in the tool's own schema, ranked DateInput below a prop that mentions a date picker in passing and below every --bulma-dateinput-* variable. Every word of a multi-word query scored the same in a name as in prose. A one-word "datepicker" matched nothing. A prop named like a component (DateInput.popover) tied with Popover itself and won on alphabetical order. And get_component({ name: "DatePicker" }) offered no suggestion, since edit distance cannot see that DatePicker means DateInput.

In a multi-word query, a word in a name now counts for more than the same word in prose. A component the query is about, one it names or whose summary has every word, ranks above props and CSS variables that match equally. A summary that shares one word gets no lift, so "at a time" does not pull Collapses into "time picker". A small table of what other libraries call a component (date picker, time picker, combobox, toggle, drawer and the like) finds it as if it had been named, however the phrase is spaced, hyphenated or cased. Unknown names are suggested from the same table first. A test holds every name in the table to the catalog. Examples that share a heading are listed once.

The tests rank a table of realistic queries, including the issue's, and check that an exact prop name still answers with that prop.

Left out: the CSS-variable index attributes a variable to the last component that declares it, so --bulma-dateinput-* reads "declared by DateTimeInput". That is a separate attribution problem in the generator.

pnpm all passes locally.

Fixes #934

…entions

search_bestax("date picker"), the example in the tool's own schema, put DateInput 33rd.
Every word of a multi-word query scored the same in a name as in prose, so a prop that
mentions a date picker in its description outranked the component whose name says
"date", and a one-word "datepicker" matched nothing at all. A prop named like a
component (DateInput.popover) tied with Popover itself and won on alphabetical order.

In a query of several words, a word in the name now counts for more than the same word
in the prose. A component the query is about, one it names or whose summary has every
word, ranks above props and CSS variables that match equally. A small table of what
other libraries call a component (date picker, time picker, combobox, toggle, drawer,
and the like) finds it as if it had been named, however the phrase is spaced, hyphenated
or cased, and get_component suggests from the same table, so DatePicker offers DateInput.
Examples that share a heading are listed once rather than once per example.
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 10 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: allxsmith/bestax/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7d36533e-ec15-42f5-bd64-ebfd0bd88770
📥 Commits

Reviewing files that changed from the base of the PR and between 90499a7 and 2e2a1ef.

📒 Files selected for processing (3)
  • bestax-mcp/src/__tests__/search.test.ts
  • bestax-mcp/src/__tests__/server.test.ts
  • bestax-mcp/src/search.ts
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://9f11a028.bestax.pages.dev

@allxsmith

Copy link
Copy Markdown
Owner Author

deep-review: fresh

The previous run was cancelled at the runner limit before it posted anything, so this asks for the same full review again. The change is the ranking and alias logic in bestax-mcp/src/search.ts; start there, then its table of realistic queries in search.test.ts and the two server.test.ts cases. Any files under bestax-mcp/data/ are generated by pnpm gen:mcp and CI's gen:mcp:check holds them to the source.

Comment thread bestax-mcp/src/search.ts Outdated
Comment thread bestax-mcp/src/search.ts
Comment thread bestax-mcp/src/search.ts

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review — 1 blocking · 2 advisory

# Severity Area Finding Location
1 🟡 Minor Correctness Multi-word name bonus is un-gated for props/examples while COMPONENT_BONUS needs full prose coverage, so a partly-matching natural-language query now ranks a prop or example above the right component (Block, Reveal regress vs main) bestax-mcp/src/search.ts:242
2 🔵 Advisory Correctness An alias word inside a longer query (110–128) outweighs the component that query names outright (~80): breadcrumbs with separators → Divider bestax-mcp/src/search.ts:163
3 🔵 Advisory Correctness t.length >= 3 && lowerName.includes(t) is boundary-free, so tab scores on Table exactly as on Tabs and tab panel returns Table before Tabs bestax-mcp/src/search.ts:101

Overall: Sound, and a large measured improvement — I reimplemented the origin/main scorer alongside HEAD and ran both over the committed index. Every claim in #934 reproduces on main (date picker → Popover.closeOnEscape first, DateInput at 33; datepicker → nothing; the four *.popover props tied with Popover at 130 and won alphabetically) and every one is fixed at HEAD. A sweep deriving a 3-word query from each of the 90 catalog purposes found zero cases where a prop, example, CSS variable or skill outranks the component the query describes, which is the central claim held empirically. The riskiest part is the one place the new scoring is asymmetric between hit kinds (finding 1): the name bonus in score() applies to props and example titles, including function words like that and into, while a component is denied COMPONENT_BONUS unless its summary covers every term — and the 26-row pinned table is all short keyword queries, so nothing covers that shape. Start there; findings 2 and 3 are ranking trade-offs worth knowing but not worth holding the merge for.

Residual risk:

  • Prose-only queries — open, and posted as finding 1. vertical spacing between elements and element that animates into view both move the right component out of #1 relative to main. A majority-coverage gate fixes both and keeps the pinned table, the closeOnEscape exact-prop case and the popover component-above-prop case green (verified by running all three against a patched scorer).
  • Alias dominance in compound queries — open, posted as finding 2; bounded to component-vs-component ordering, never component-vs-prop.
  • Alias table drift — refuted. The names only components that exist test holds every target in ALIASES to the catalog, so a rename fails the suite rather than silently pointing nowhere. Object.hasOwn guards the lookup, so a query word like constructor or toString cannot reach a prototype key.
  • Dedup dropping distinct results — refuted. The key is kind + NUL + name; part.path is unique across all 90 component files (checked — every path occurs exactly once), so no two prop hits can collide, and cssVarIndex / component / skill names are unique by construction. For examples the collision is real and intended (DateInput has three Month and Year Pickers bodies and three Launcher Icon bodies) and nothing is lost: the surviving row names get_examples({ component, query: title }), which filters on title.includes(q) and returns all of them.
  • Performance — refuted. aliasMatches runs once per search rather than once per component, and phrases is O(4·words). A full-length 200-character query (the MAX_QUERY ceiling) over all five kinds completes in ~35 ms and date picker in ~7 ms, nowhere near the 5.8 s the file cache note is about. STEM_RE_CACHE is untouched.
  • Coverage and gates — refuted. pnpm --filter bestax-mcp test:coverage is green at 288 tests, with search.ts at 98.56% statements / 92.85% branches against the 95/78 floor; tsc --noEmit and prettier --check both clean. Nothing under bestax-mcp/data/ changed, so gen:mcp:check is unaffected, and docs/docs/guides/llms/index.md describes search_bestax only as "components, props, examples, CSS variables and skills in one ranked list", which stays true.
  • PR body — accurate. Spot-checked the two non-obvious claims: cssVarIndex["--bulma-dateinput-cell-color"] really is DateTimeInput (the attribution problem the body defers), and the Collapses purpose really does say "one item open at a time", so the time picker example is the right one.

🏄 Pretty clean set, this one — paddled straight out past the --bulma-dateinput-* soup that used to swamp "date picker" and came back with DateInput on the nose. One little shorebreak where the long chatty queries lose the component to a stray prop, so patch that and it is all glass from here.

…nce-shaped search

A query written as a sentence lost its component again. A component got its bonus only when
its summary had every word, so Block, which says "margin" where the query said "spacing", lost
"vertical spacing between elements" to a prop named spacing, and an example titled with the
word "that" beat Reveal. A summary with most of the query's words now earns the bonus, and
words like "that", "with" and "into" are no longer terms at all.

An alias also outweighed a component the query named outright, so "breadcrumbs with
separators" answered with Divider. Where a query names another component outright, an alias
is now worth what that name is, and a plural counts as a name.
@allxsmith

Copy link
Copy Markdown
Owner Author

deep-review: verify

Every open thread has a reply: fixed, with the commit, or refuted with a reason. This settles them on 2e2a1ef.

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://cc96de89.bestax.pages.dev

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review (verify) — 3 resolved · 0 open

# Severity Area Thread Outcome Location
1 🟡 Minor Correctness Name bonus un-gated for props/examples while COMPONENT_BONUS was all-or-nothing on prose coverage ✅ Verified fixed bestax-mcp/src/search.ts:311
2 🔵 Advisory Correctness An alias buried in a longer query outweighed the component that query named outright ✅ Verified fixed bestax-mcp/src/search.ts:260
3 🔵 Advisory Correctness A three-letter term scores anywhere inside a name (tab in Table) ✅ Reason accepted bestax-mcp/src/search.ts:149

What I checked

  • Thread 1. The about gate is now a strict majority (covered * 2 > terms.length) with a name/alias path, and that / with / into are in STOP_WORDS, so a function word never becomes a term. Both queries I reported are pinned at search.test.ts:138-139. The load-bearing detail: that table block loads the full component corpus in its own beforeAll (catalog.components.map(loadComponent), line 101), not the four records the outer suite loads — so prop:Avatars.spacing and the ClientOnly example really are in the haystack those rows score against. Both now return the component at #1.
  • Thread 2. aliasMatches takes the catalog names and drops the alias to NAME_WORD_SCORE when the query names a component the alias does not reach. Traced breadcrumbs with separators by hand: with is a stop word, breadcrumbs reaches Breadcrumb through the new plural-aware isName, and separator reaches only Divider — so Divider takes 30 rather than 118 and Breadcrumb is #1. star rating (line 124) still passes, which is what holds the empty-outright case where every is vacuously true and the full ALIAS_SCORE survives.
  • Thread 3. Refutation from the PR author (allxsmith, live role admin). It holds on its own terms: names are lower-cased before matching, which erases the word boundaries a boundary-anchored rule needs, so such a rule could not match the term input against dateinput for exactly the reason this file already documents for them against anthem — and date input has to reach DateInput. Table versus Tabs is the shared-prefix collision my own follow-up comment had already withdrawn the fix for.
  • Tests. pnpm --filter bestax-mcp test green at 295/295 (7 suites); search.test.ts alone at 60/60. The two guards that bound the widened bonus — closeOnEscape answering with a prop, and popover putting the component above DateInput.popover — both still pass, so the looser about gate is not lifting components over props that genuinely win.

Overall: This pass settled threads and reviewed no commits. All three of my open threads are resolved: two verified fixed against the current code and the real committed index, one accepted on a reasoned deliberate-choice refutation from the author. Nothing is left open, and the two fixes are pinned by assertions that run over the full corpus rather than a reduced fixture, which is what makes them durable.

Residual risk: Not re-assessed — a verify pass settles what the prior review left open and raises nothing new. A reader who wants the current ranking logic reviewed afresh should ask for a fresh pass.

🏄 Three threads paddled out, three came back in clean — two real fixes pinned against the whole catalog, and one gnarly tab-slash-Table prefix thing nobody can surf around, which the author called straight. All mellow, good to go.

@github-actions github-actions Bot added the review-converged Deep review converged: no blocking findings, all review threads resolved, checks green label Oct 8, 2026
@allxsmith allxsmith added the needs-human-review Loop converged (or contested): awaiting owner review + manual merge label Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

📸 Story screenshots at handoff — 2e2a1ef

No Storybook stories map to this PR's changed files — nothing to screenshot. (workflow run)

@allxsmith
allxsmith merged commit 7a41385 into main Oct 8, 2026
81 checks passed
@allxsmith
allxsmith deleted the fix/934-search-ranking branch October 8, 2026 04:04
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.27.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 4.2.14 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 2.25.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 1.14.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deep-review needs-human-review Loop converged (or contested): awaiting owner review + manual merge released review-converged Deep review converged: no blocking findings, all review threads resolved, checks green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bestax-mcp's search ranks DateInput near last for "date picker"

1 participant