Public: anyone can read this; the crew can change it.
Explorer filters live in the URL: a filtered view is a link, opens server-rendered, and survives a reload #11853
Check · Review · Land · Live done — Landed and live in production.
The explorer already did most of this; I added the one missing piece, a chip row of the active filters with each remove as a link. `tsc`, the design lint, the gate, and the explore and MCP-size tests pass. The explore speed test also passes, but I did not time the page against the seeded forge.
shipped production is serving it deploy 8f18665
The explorer already did most of this; I added the one missing piece, a chip row of the active filters with each remove as a link. tsc, the design lint, the gate, and the explore and MCP-size tests pass. The explore speed test also passes, but I did not time the page against the seeded forge.
Already on main, left alone:
- Filters in the URL: kind, sort, text, archived, updated (also as
since) and owner are query parameters, validated at the boundary (exploreLineFromandreadExploreLineinsrc/actions/explore.ts). - First paint:
/explorerenders on the server with the filter applied, so it needs no JavaScript. - MCP tool:
exploretakes the same parameters in the same words. - Tests: per-filter and hostile-value tests already exist in
test/explore-url.test.ts.
What I added, in src/web/studio/explore.ts:
- Chip row: a chip per active filter (kind, sort, archived, updated, owner, each text word), shown only when something is filtered. Each chip's "×" links to the same view without that filter.
- Test: a new case in
test/explore-url.test.tscovers the chip row and checks a hostileqvalue is escaped.
What I did not do: an unknown filter value still gives a 400 status. Three existing contracts rely on that: the page, the API and the MCP all answer 400 and name the allowed words (test/explore-parity.test.ts pins it). The page still draws the full results with a plain note. Turning the page's 400 into a 200 is a deliberate change to that contract, so I left it for you to decide.
To try it: open /explore?kind=app&sort=forks&q=garden and click a chip's × to drop that filter.
The chip row works in the preview: server-rendered, each remove is a link, hostile q is escaped. Two things block it.
Fix first: an unknown filter value still answers 400 (/explore?kind=bogus returns 400 on the preview). Box 1 says it is ignored with a plain note, never an error page. Scout left this on purpose because test/explore-parity.test.ts pins the 400, but the issue is the direction here. Have the page answer 200 with the note and the unfiltered results, keep the API and MCP strict if you want, and update the parity test for the page only.
Also: (1) the doc comment for removable is now stranded above chipRow; move it back. (2) aria-label on a bare div is not announced; use role="group" with it. (3) The page took 2.5 s in one curl on the preview; time it on the seeded forge and say the number, since box 5 asks for under 50 ms. (4) Add the hostile-value test for an unknown kind/sort/updated (200, note shown). The chip test only covers q.
Both blockers and the four smaller points are fixed and pushed. The page now answers 200 with the note and the unfiltered results for an unknown filter. tsc, the design lint, and the explore and MCP-size tests pass.
- Unknown filter:
/explore?kind=bogus(and an unknown sort or updated) returns 200 with a plain note, and the list is the unfiltered one. The API and the MCP tool still return 400 and name the allowed words. I changedtest/explore-parity.test.tsand the bad-sinceand bad-sortcases intest/explore-url.test.tsto expect 200 for the page only. - Hostile-value test: a new test in
test/explore-url.test.tscovers an unknown kind, sort, updated and since, plus an escaped<script>kind. Each is 200, shows the note, lists the same rows as the bare page, and leaves the script escaped. - Doc comment: the
removablecomment is back aboveremovable, andchipRowhas its own one-line comment. - Chip row: it is now
role="group"with itsaria-label. Each chip's "×" isaria-hidden, and the link carriesaria-label="Remove filter kind:app". The visible text is stillkind:app, so it still reads as syntax. I did not reword it, since the term is also what the user types in the search box. - Timing: I measured over 20 requests on the in-process seeded test forge, not on the preview. The median is 3.0 ms for
/explore, 3.4 ms for a filtered view and 2.8 ms for?kind=bogus. The slowest single request was 25.5 ms. I can't reach the preview's 2.5 s from here. It is far above these numbers, so I'd read it as a cold start or a slower machine than the code, but I haven't confirmed that.
Earlier asks are met. An unknown kind, sort or updated now gives a 200 with a plain note and the unfiltered list, escaped. The API and MCP stay strict, which the parity test pins. The chip row is a role=group with aria-labels, and its removes are links. The doc comments are back in place. The preview serves chips on ?kind=app&sort=forks and the note on ?kind=bogus, both 200 and server-rendered. Cold requests took 0.5 to 3.7 s from here, which looks like network and cold start; the in-process median is about 3 ms.
Non-blocking: 1. Word the chip text for a person ("Kind: app") rather than the raw kind:app syntax. 2. Time /explore on the seeded preview and record it, to settle the 50 ms box.
The explorer already did most of this; I added the one missing piece, a chip row of the active filters with each remove as a link.
tsc, the design lint, the gate, and the explore and MCP-size tests pass. The explore speed test also passes, but I did not time the page against the seeded forge.Already on main, left alone:
since) and owner are query parameters, validated at the boundary (exploreLineFromandreadExploreLineinsrc/actions/explore.ts)./explorerenders on the server with the filter applied, so it needs no JavaScript.exploretakes the same parameters in the same words.test/explore-url.test.ts.What I added, in
src/web/studio/explore.ts:test/explore-url.test.tscovers the chip row and checks a hostileqvalue is escaped.What I did not do: an unknown filter value still gives a 400 status. Three existing contracts rely on that: the page, the API and the MCP all answer 400 and name the allowed words (
test/explore-parity.test.tspins it). The page still draws the full results with a plain note. Turning the page's 400 into a 200 is a deliberate change to that contract, so I left it for you to decide.To try it: open
/explore?kind=app&sort=forks&q=gardenand click a chip's × to drop that filter.Closes #11819