fix(skills): the Settings list and the composer picker match a query word by word (F5) - #289
Merged
Conversation
…word by word (F5) QA finding F5's third and fourth copies. Both surfaces filtered installed skills by asking whether the WHOLE lowercased query occurred inside one field, so a phrase naming two installed skills found neither, and a one-letter query found every row whose prose held that letter. Measured on this tree before the change, over the rows the two pickers render: `R scripting ggplot visualization` listed 0 of 3 rows on both surfaces while `ggplot` alone listed 1; `R` listed 2 of 2 rows, one of them `markdown-render`, which holds the letter twice and means nothing by it. In the composer that second case is a bulk write, not just a long list: "Enable all" writes every row the filter left on screen, and it read "Enable all (2)". Both now go through `skills/searchCatalog.ts`, which is not a matcher — it is the field list. The rules stay in `baam/search.ts`, the port of `crates/biorouter/src/catalog_search.rs`, so these two pickers, the Browse modals and the model's own `skills__searchSkills` read the same words the same way. Two decisions the shared matcher could not settle, both recorded in the new file and pinned by its tests: - A bundle ROW has no counterpart in Rust, where a member's name is the Name field of its own entry. Member names are searched at Label: on a bundle row a member's name is not what the row is called, it is a label saying what the row contains. Measured for the query `ggplot`, a skill of its own by that name scores 39 against a package containing a member of that name at 36. At Name both score 39 and the tie falls to catalog order, which lists every bundle first — so the package would win every such query. Flipping the weight was run once: the assertion goes red with [39, 39]. - SkillsView grouped by provenance, which discards the rank. Under a query it is now one ranked "Matches (n)" list, as BrowseSkillsModal does; browsing keeps the headings. Deletability moved from the group to the row with it, since a Matches list mixes provenances and an extension-supplied skill must still offer no Delete. Rust is untouched: the field weights for a single skill are exactly `search_fields` in agents/skills_extension.rs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
QA finding F5's third and fourth copies.
SkillsView(Settings → Skills) andBottomMenuSkillSelection(the composer's skill menu) each filtered installed skills by asking whether the whole lowercased query occurred inside one field — so a phrase naming two installed skills found neither, and a one-letter query found every row whose prose happened to hold that letter.Measured on
origin/main(5a404ec), before the changeThe new tests were written first and run against unmodified
main: 5 failed, 32 passed.R scripting ggplot visualization(3 rows:ggplot,pdf,r-scripting)r-scripting, thenggplotUnable to find role="menuitemcheckbox"r-scripting, thenggplotggplotaloneR(2 rows:markdown-render,r-scripting)r-scriptingonlyR, "Enable all"Enable all (2)Enable all (1)That last row is why the letter leak is not just a long list: "Enable all" writes every row the filter left on screen, so a leaking filter is a bulk write over rows the user never asked about.
After the change, the same suites are green: 71 passed across
src/components/skills/+BottomMenuSkillSelection.test.tsx; 246 passed includingsrc/components/baam/; 155 passed across the eleven downstream suites that mountChatInput,Approuting orHub.The change
Neither surface got a matcher. They got a field list: the new
ui/desktop/src/components/skills/searchCatalog.tsimportsbaam/search.ts— the port ofcrates/biorouter/src/catalog_search.rs— and says only what text of an installed row is searched and what a match there is worth. These two pickers, the Browse modals and the model'sskills__searchSkillsnow read the same words the same way.baam/search.tswas deliberately not moved. The matcher is shared by import; only its directory name is now narrower than its callers. A rename would rewrite four BAAM files, and the header that has to stay accurate — the one warning that a rule change is a change to three files,landing/marketplace-search.jsincluded — belongs to that file.Decision 1 — what a member name is worth on a bundle row
A bundle ROW has no counterpart in Rust:
search_fields(agents/skills_extension.rs) ranks individual skills, so a member's name is theNamefield of a different entry. Here the package is the row — in the composer only the package can be toggled — so member names must be searchable or a package is unfindable by what it contains.They are searched at
Weight.Label, argued rather than ported: on a bundle row a member's name is not what the row is called, it is a label saying what the row contains — the role a tag plays on a marketplace card.The weight is load-bearing, and the scores are asserted rather than just the order. For the query
ggplot, measured on this tree: a skill of its own by that name scores 39, a package merely containing a member calledggplotscores 36. AtNameboth score 39 and the tie falls to catalog order — which lists every bundle before every single skill, so the package would silently win every such query. I flipped the weight toNameonce to check the assertion can fail: it goes red withexpected [ 39, 39 ] to deeply equal [ 39, 36 ].A single skill's fields are left exactly as Rust has them — name →
Name, description →Prose, bundle →Label— so this PR changes how the installed catalog is matched, not which fields are read.Decision 2 —
SkillsViewwas throwing the ranking awayIt re-grouped by provenance (Biorouter / per-extension / other-agent / project), which discards the rank: a Biorouter skill matching one word of the query would sit above a project skill matching all of them. Under a query it is now one ranked "Matches (n)" list, copying
BrowseSkillsModaldown to the heading; with an empty query the provenance headings are unchanged.Deletability moved from the group to the row with it — a Matches list mixes provenances, and an extension-supplied skill must still offer no Delete. A test asserts exactly that inside the Matches list.
Deliberately not changed
baam/search.tsalready citescatalog_search.rsand already portswrittenIn, so the staleness the chip predicted had been fixed before this branch started.landing/marketplace-search.jsandlanding/baam.htmlstill carry the website's own copy of the F5 defect. Out of scope here and noted so it is not mistaken for fixed.slugis still not searched on either side, and was not searched by the filter this replaces.Gates
npm run lint:checkexit 0 (tsc --noEmit, eslint --max-warnings 0, themes, 332 contrast assertions, token mirrors) andnpx prettier --checkclean onsrc/components/skills/andsrc/components/bottom_menu/.Closes chips task_a781334f and task_359d7a24, which describe these same two surfaces from either side.
🤖 Generated with Claude Code