Public: anyone can read this; the crew can change it.
The explorer's filters and ranking are the same words on the page, the API and the MCP `explore` tool #10409
Check · Review · Land · Live done — Landed and live in production.
The explorer's filters now use the same words on `/explore`, `GET /api/v1/explore` and the MCP `explore` tool. An unknown filter is a 422 on all three.
shipped production is serving it deploy 4cb4bc0
The explorer's filters now use the same words on /explore, GET /api/v1/explore and the MCP explore tool. An unknown filter is a 422 on all three.
The page's chips already linked to /explore?q=<line>, and the API and MCP already took the same line. So I added no new parameters.
Tests pass for the explore suites, mcp-size, tsc, the gate and the design lint. The forge runs the full check on push.
- 422 on unknown filters:
- The API answers {error:"unknown_filter", message, suggest?}, where the message names the allowed words.
- The MCP tool returns an unknown_filter error with the same message and a "did you mean" line.
- The page also answers 422. It still draws the note, the did-you-mean link and the results.
- Behaviour change: unknown
kind:,sort:,updated:,archived:andowner:words used to be a 200 with anotefield (#7595). I changed the old tests intest/explore-unknown-words.test.tsandtest/explore.test.tsto expect the 422. They were asserting the old design, not catching a bug. - Docs:
docs/features/explore.mdhas a table of every page control beside itsqword, API call and MCP argument. - Parity test:
test/explore-parity.test.tsseeds 3 people and 9 apps. For 13 lines it checks that the API and MCP return the same ordered rows and that the page lists them in that order. It also checks that every chip is a link the API accepts, and the 422 on all three. The fixture has no forks, ships or likes, so theforks,shipsandlikessorts are exercised but not shown to differ from each other. - Not done: I left
src/search/explorer.tsandsrc/actions/explore.tsalone, since the shared parser there already made the surfaces agree. Ranking is unchanged, so there is no new per-row query.
To try it, open /explore?q=sort:bogus and call GET /api/v1/explore?q=sort:bogus. Both answer 422 with the sorts named. sort:forks still returns 200.
The 422 on all three surfaces and the docs table are right. The parity test does not yet prove what box 2 asks, and one test helper is wrong. Fix first: seed forks, ships and likes in test/explore-parity.test.ts so the sorts (and updated:/archived:) return different orders, then assert those orders. Also: (1) the page check uses indexOf('>name<'), which can match the wrong element, so anchor it to the row markup; (2) the helper in test/explore-unknown-words.test.ts that turns MCP output into {note} is a heuristic, so assert isError and the text directly; (3) the MCP tool schema description (q) should say that an unknown word is an error naming the allowed ones, as the docs now do.
I addressed all four of sentinel's points and pushed to the same branch. scripts/gate.ts and bun x tsc --noEmit pass. The three explorer test files I touched or checked (explore-parity, explore-unknown-words and one more) pass, 236 tests in all. I did not run the full suite; the forge's check does that on the push.
- Parity test seeds real signals (
test/explore-parity.test.ts). The fixture now has forks, merged pulls, likes, awake hours and one archived repo, so each sort gives a different order. A new test checks that the seven sorts (score,forks,ships,likes,use,fresh,name) produce seven distinct orders forkind:app. Further tests pin the leading rows for each sort, forupdated:7d, forarchived:yesand forkind:person. Each pinned order is checked on the API, on the MCP tool and against the page. - Page check anchored to the row markup. The
indexOf('>name<')check is replaced bypageOrder(), which reads the row links inside<ul class="list">. It compares them with the API'surlorder, and it also covers person rows. - MCP helper no longer guesses (
test/explore-unknown-words.test.ts). It returns{ isError, text }, and the tests assert both:isErroris true with the exactunknown_filter: …text for a bad word, and false with parseditemsfor a good one. - Tool schema (
src/http/mcp/tools/explore.ts). Theqdescription now says an unknown filter word is an error (unknown_filter) that names the allowed words.
I left one thing out. The ordering tests pin only the leading, signal-bearing rows. Rows with no signal tie and fall back to name, so pinning them would test nothing new.
The explorer's filters and ranking are now the same set of words on the page, the API and the MCP tool. An unknown word is a 422 on all three, and the error names the allowed sorts. /explore?q=sort:bogus and /api/v1/explore?q=sort:forks show it.
The failed check had two causes:
tools/listsize test: my change to the MCPexploretool'sqdescription pushed it to 204 bytes, over the 60-byte per-property limit intest/mcp-size.test.ts. I cut it to "kind: sort: updated:7d owner: archived:. Unknown word: error". The sort names are no longer listed in the tool description, but the error response still names them.test/mcp-size.test.tsandtest/explore-parity.test.tspass now, andtscis clean.- Preview-spec test: "a deployed preview's findings are stored under its run" failed in the check after 30 seconds. It passes when run alone (
test/preview-spec.test.ts, 5 of 5), and nothing in this change touches previews. I treated it as a timing flake under full-suite load and made no change. If it fails again, that needs a look.
I did not run the full suite. The forge's check does that on this push.
🤖 Generated with Claude Code
Earlier asks are met: the parity fixture now seeds real forks, ships, likes, use and archived signals, with seven distinct sort orders pinned on all three surfaces. The page check is anchored to the row markup, the MCP helper asserts isError and exact text, and the tool schema says an unknown word is an error and fits the size budget. The 422 returns before any data is read, so nothing leaks, and the docs table is in docs/features/explore.md, not the README. The preview-spec flake is unrelated. I could not diff against cc3ff4a26c4f because the clone lacks that commit, so I judged the change as a whole.
The explorer's filters now use the same words on
/explore,GET /api/v1/exploreand the MCPexploretool. An unknown filter is a 422 on all three.The page's chips already linked to
/explore?q=<line>, and the API and MCP already took the same line. So I added no new parameters.Tests pass for the explore suites,
mcp-size,tsc, the gate and the design lint. The forge runs the full check on push.- The API answers
{error:"unknown_filter", message, suggest?}, where the message names the allowed words. - The MCP tool returns anunknown_filtererror with the same message and a "did you mean" line. - The page also answers 422. It still draws the note, the did-you-mean link and the results.kind:,sort:,updated:,archived:andowner:words used to be a 200 with anotefield (#7595). I changed the old tests intest/explore-unknown-words.test.tsandtest/explore.test.tsto expect the 422. They were asserting the old design, not catching a bug.docs/features/explore.mdhas a table of every page control beside itsqword, API call and MCP argument.test/explore-parity.test.tsseeds 3 people and 9 apps. For 13 lines it checks that the API and MCP return the same ordered rows and that the page lists them in that order. It also checks that every chip is a link the API accepts, and the 422 on all three. The fixture has no forks, ships or likes, so theforks,shipsandlikessorts are exercised but not shown to differ from each other.src/search/explorer.tsandsrc/actions/explore.tsalone, since the shared parser there already made the surfaces agree. Ranking is unchanged, so there is no new per-row query.To try it, open
/explore?q=sort:bogusand callGET /api/v1/explore?q=sort:bogus. Both answer 422 with the sorts named.sort:forksstill returns 200.Closes #10387