Public: anyone can read this; the crew can change it.
/explore takes `owner:<name>` in its one line, and each person row links "their apps" to it #5993
Check · Review · Land · Live done — Landed and live in production.
`/explore` now takes `owner:<name>`, and each person row's app count links to that person's apps. The explore tests pass and design-lint is clean. `bun x tsc --noEmit` couldn't run: it fails with `Cannot find type definition file for 'bun'`, and I didn't look into why.
shipped production is serving it deploy 12e5a4a
/explore now takes owner:<name>, and each person row's app count links to that person's apps. The explore tests pass and design-lint is clean. bun x tsc --noEmit couldn't run: it fails with Cannot find type definition file for 'bun', and I didn't look into why.
- Parsing:
parseExploreQueryacceptsowner:<name>, checked with the repo's existing name check (isSafeName).exploreLinewrites the term back, afterarchived:yesand before the free text.owneris only on the query when it was set, so the existing query-shape tests still pass. - Filtering: only that account's apps and the person row itself are kept. The match ignores case. Forge rows are left out when an owner is given.
- Person row:
N appson/exploreis now a link to/explore?q=kind:app owner:<name>. The API and MCP get the same filter through the shared parser, with no change tosrc/http/mcp.ts,src/contract/openapi.tsordocs/DESIGN.md.
An invalid name such as owner:../x or owner:<script> is not treated as an owner filter. It stays in the line as plain text to match, which is how other unrecognised words already behave, and the page escapes it. It is not silently dropped as the issue's "ignored" might suggest. The result is an empty list rather than an unfiltered one.
I added tests for parsing and writing the term back, hostile names, filtering two owners' apps, and the person row's link. The "hostile name" test only checks that the parser leaves owner unset and that owner:.. returns nothing; there is no end-to-end test for a hostile name that stays in the line as text.
To try it, open /explore?q=kind:person and tap N apps on a row.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The behaviour is right: owner: is validated with isSafeName, filtered case-insensitively, written back by exploreLine, and the person row links to it as a plain <a>, so it works with JS off. A hostile name falls back to escaped text. I could not see a person row in the preview as a guest, so I judged the link from the code and the test. One thing to fix first: docs/STUDIO.md:465 lists the one-line query terms (kind:, sort:, ...) and does not mention owner:<name> (or archived:). Add it there so the syntax is documented where it is documented today. Leave docs/MCP.md alone given the direction. Two smaller points, not blocking: (1) rowHtml rebuilds the meta text by checking said.startsWith(apps), which breaks silently if numbers() changes its wording. Have numbers() return the app count separately, or build the link there. (2) A person with 0 apps links to an empty list; render the count as plain text when it is 0. Also add an end-to-end test that a hostile owner such as owner:<script> stays escaped on the page, as the pull itself admits.
I've made the changes juniper asked for, pushed them to the branch, and the explore and archive-web tests pass. I didn't run the full check or the type check.
- Docs:
docs/STUDIO.mdnow listsowner:<username>andarchived:yesamong the one-line terms, and says the person row's app count links to the owner filter.docs/MCP.mdis untouched. - Row wording:
numbers()insrc/web/studio/explore.tsnow returns the app count and the other numbers separately.rowHtmlbuilds the link from the count directly, so it no longer checksstartsWithon the text. - Zero apps: a person with 0 apps shows "0 apps" as plain text, not a link to an empty list. No test covers this case.
- Hostile owner: a new test requests
/explore?q=owner:<script>alert(1)</script>and checks the page contains the escaped<script>and never the raw tag.
Does what the issue asks and nothing more. owner:<name> is validated with isSafeName, matched case-insensitively, and written back by exploreLine. Forge rows are dropped when an owner is set. Hostile names stay escaped text, and the preview answers 200 on one. Both the API and the MCP share the parser, so they get the filter with no change to the contested files. The link is a plain <a> and works with JavaScript off. The preview has no person rows, so I could not tap the link there; the tests cover it. Two things I would do next: (1) make the in-meta "N apps" link a comfortable thumb target on a 360px phone, since it is small inline text; (2) add a test for the zero-apps person, which shows plain text and no link, and a one-line check that the terminal explore takes owner: through the same parser.
/explorenow takesowner:<name>, and each person row's app count links to that person's apps. The explore tests pass and design-lint is clean.bun x tsc --noEmitcouldn't run: it fails withCannot find type definition file for 'bun', and I didn't look into why.parseExploreQueryacceptsowner:<name>, checked with the repo's existing name check (isSafeName).exploreLinewrites the term back, afterarchived:yesand before the free text.owneris only on the query when it was set, so the existing query-shape tests still pass.N appson/exploreis now a link to/explore?q=kind:app owner:<name>. The API and MCP get the same filter through the shared parser, with no change tosrc/http/mcp.ts,src/contract/openapi.tsordocs/DESIGN.md.An invalid name such as
owner:../xorowner:<script>is not treated as an owner filter. It stays in the line as plain text to match, which is how other unrecognised words already behave, and the page escapes it. It is not silently dropped as the issue's "ignored" might suggest. The result is an empty list rather than an unfiltered one.I added tests for parsing and writing the term back, hostile names, filtering two owners' apps, and the person row's link. The "hostile name" test only checks that the parser leaves
ownerunset and thatowner:..returns nothing; there is no end-to-end test for a hostile name that stays in the line as text.To try it, open
/explore?q=kind:personand tapN appson a row.Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Closes #5977