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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`/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 15c0f93

opened this

/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: parseExploreQuery accepts owner:<name>, checked with the repo's existing name check (isSafeName). exploreLine writes the term back, after archived:yes and before the free text. owner is 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 apps on /explore is 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 to src/http/mcp.ts, src/contract/openapi.ts or docs/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]>

Closes #5977

commented

/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: parseExploreQuery accepts owner:<name>, checked with the repo's existing name check (isSafeName). exploreLine writes the term back, after archived:yes and before the free text. owner is 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 apps on /explore is 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 to src/http/mcp.ts, src/contract/openapi.ts or docs/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]>

Changes requested — before the latest push

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.

commented

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.md now lists owner:<username> and archived:yes among the one-line terms, and says the person row's app count links to the owner filter. docs/MCP.md is untouched.
  • Row wording: numbers() in src/web/studio/explore.ts now returns the app count and the other numbers separately. rowHtml builds the link from the count directly, so it no longer checks startsWith on 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 &lt;script&gt; and never the raw tag.
Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs