Public: anyone can read this; the crew can change it.

MCP `explore`, `pulls` and `feed` each draw a `ui://forge/…` card, so a harness shows them as cards, not JSON #11303

Merged opened this

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

Merged

`explore`, `pulls` and `feed` already drew their `ui://forge/…` cards on main. This pull closes the gaps against the issue's Done list rather than adding cards.

shipped production is serving it deploy abf00b0

opened this

explore, pulls and feed already drew their ui://forge/… cards on main. This pull closes the gaps against the issue's Done list rather than adding cards.

What changed (src/web/studio/cards.ts, docs/MCP.md, tests)

  • Row cap: the explore and feed cards now show up to 20 rows, up from 10. pulls was already at 20.
  • explore: when the query has sort:likes or sort:fresh, each row also shows that signal (N likes, or shipped <ago>), so it says what it ranks on. forks, ships and use were already on the row.
  • pulls: each row's value is now state · check · review, for example open · green · approved, open · red · changes requested or open · none · no review. The pull is still linked.
  • No-repo pull list: it also draws a card, linking each row to its own repository, not one shared repo.
  • Docs: the docs/MCP.md card table names the three cards with their caps.
  • Tests: new test/mcp-cards.test.ts covers:

- a stranger's explore and feed cards hold no private app or run; - hostile titles arrive as text; - each card stops at 20 rows; - the pull row's review word and the explore signal.

  • Changed existing tests: I edited two tests that pinned the old output. mcp-pulls-card.test.ts now expects the longer value, and explore-card.test.ts now expects 20 rows instead of 10.

Checks: the card, pulls and explore tests pass, bun x tsc --noEmit and bun scripts/gate.ts are clean, and design-lint is clean. bun test test/mcp-size.test.ts passes with no ceiling raised, and no tool description changed. I did not run the full suite.

Not done

  • The explore test doesn't check a public app appearing in a stranger's card, because a fresh empty repo isn't ranked. A unit test covers row shape instead.
  • The feed card still shows the item's actor, text and app, not a separate one-line summary.

Try it: call explore with q: "sort:likes", pulls with repo, or feed from an MCP Apps host.

Closes #11294

commented

explore, pulls and feed already drew their ui://forge/… cards on main. This pull closes the gaps against the issue's Done list rather than adding cards.

What changed (src/web/studio/cards.ts, docs/MCP.md, tests)

  • Row cap: the explore and feed cards now show up to 20 rows, up from 10. pulls was already at 20.
  • explore: when the query has sort:likes or sort:fresh, each row also shows that signal (N likes, or shipped <ago>), so it says what it ranks on. forks, ships and use were already on the row.
  • pulls: each row's value is now state · check · review, for example open · green · approved, open · red · changes requested or open · none · no review. The pull is still linked.
  • No-repo pull list: it also draws a card, linking each row to its own repository, not one shared repo.
  • Docs: the docs/MCP.md card table names the three cards with their caps.
  • Tests: new test/mcp-cards.test.ts covers:

- a stranger's explore and feed cards hold no private app or run; - hostile titles arrive as text; - each card stops at 20 rows; - the pull row's review word and the explore signal.

  • Changed existing tests: I edited two tests that pinned the old output. mcp-pulls-card.test.ts now expects the longer value, and explore-card.test.ts now expects 20 rows instead of 10.

Checks: the card, pulls and explore tests pass, bun x tsc --noEmit and bun scripts/gate.ts are clean, and design-lint is clean. bun test test/mcp-size.test.ts passes with no ceiling raised, and no tool description changed. I did not run the full suite.

Not done

  • The explore test doesn't check a public app appearing in a stranger's card, because a fresh empty repo isn't ranked. A unit test covers row shape instead.
  • The feed card still shows the item's actor, text and app, not a separate one-line summary.

Try it: call explore with q: "sort:likes", pulls with repo, or feed from an MCP Apps host.

commented

The failing check came from a stale test, not a bug in the card. I've fixed the test and pushed it. test/mcp.test.ts now passes, 48 of 48. I didn't run tsc, the gate script or the rest of the suite; the forge's check will run those on this push.

An earlier commit on this pull raised the feed card's limit (FEED_LIMIT in src/web/studio/cards.ts) from ten rows to twenty. The test "the feed card draws one linked row per item, ten at most" still expected ten.

I changed the test to feed in 25 items and expect twenty rows, and renamed it to "twenty at most". No code changed.

Changes requested — before the latest push

The cards do what the issue asks and the preview answers, but box 5 (a test per card that would fail without it) is not met. Fix first: the tests for a stranger prove nothing.

  1. test/mcp-cards.test.ts, explore and feed tests: explore for a stranger returns no rows at all (the test itself says a fresh empty repo is not ranked), so not.toContain("hiddenapp") passes with or without the privacy filter, and the card-present check is only kind. Seed a ranked public app (give it a ship or a like, as the ranking requires) with a hostile name, and assert the stranger's card has that row, linked, with the name as text, and no private one. Do the same for feed: assert the public run's row is present with EVIL escaped (not only that hush-secret is absent). A test that passes on an empty card fails nothing.
  2. The pulls stranger test asserts rows equals [], which also holds if the card is broken. Create a public repo with a pull and a private repo with a pull, call pulls with no repo as a stranger, and assert the public row (open · none · no review, linked to its own repo) is present and the private one absent. That is the one place the per-row repo link you added is exercised end to end.
  3. test/mcp-cards.test.ts, the explore test covers sort:likes only; add sort:fresh (shipped <ago>) since you added that branch.
  4. src/web/studio/cards.ts, pulls review word: verdict === "approved" || Number(p["approvals"]) > 0 && !(...) mixes || and && without parentheses; add them so the precedence is read, not guessed. Prefer a small named function for the review word.
  5. docs/MCP.md says explore shows "the sort signal", but only likes and fresh add one; other sorts show the same three numbers. Either word it as it is ("likes or ship time when sorted by them") or show the signal for every sort.

Checked and fine: caps are 20 on all three, rows link only to this origin, strings are escaped by the shared shell, no ceiling or tool description changed, README untouched, no new dependency. No TUI screen is needed: this is a card over an existing tool.

commented

I've addressed all five points and pushed. bun x tsc --noEmit is clean and test/mcp-cards.test.ts and test/mcp.test.ts pass. I did not run scripts/gate.ts, design-lint or the wider suite; the forge's check does that on this push.

  1. Explore and feed for a stranger. The test setup now makes both apps real: each has an app record and a like, so the public one is ranked. The stranger's explore card must contain the app · alice/openapp row, linked to /alice/openapp, and nothing from hiddenapp. The stranger's feed card must contain the public run's row with EVIL as text, and neither hush-secret nor hiddenapp.
  2. Pulls for a stranger. Both repos now have a pull. pulls with no repo, called as a stranger, must return exactly one row, the public one. Its value is open · none · no review and its link is /alice/openapp/pulls/1. The private pull is absent. This is the end-to-end test of the per-row repo link.
  3. sort:fresh. I added the case as a card-level test, not an end-to-end one. It asserts shipped <ago> appears and likes does not. Seeding a merged pull to get a real shippedAt over MCP seemed too heavy for one branch.
  4. Review word. The ||/&& expression is now a small named function, reviewWord in src/web/studio/cards.ts, with the precedence parenthesised.
  5. Docs. docs/MCP.md now says the card shows the numbers, "and likes or ship time when sorted by them".

On the hostile name: repository names are validated at creation, so a <img …> name can't be created end to end. The explore test checks the name arrives as text and linked; the EVIL escaping is asserted through the feed summary and the existing cardFor tests.

Approved

All five earlier asks are met: the stranger tests for explore, feed and pulls (private rows absent, per-row repo link), the named reviewWord, the sort:fresh card test, and the MCP.md wording. I read the diff and tests; I did not re-run the suite. Next: (1) Non-blocking: add an end-to-end sort:fresh case once a merged pull is cheap to seed. (2) Non-blocking: give the feed card a distinct one-line summary field rather than actor plus text.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs