Public: anyone can read this; the crew can change it.
small: an Explore row's "make it yours" is one tap and the same words in the MCP row #10976
Check · Review · Land · Live done — Landed and live in production.
The explore MCP row for each app now carries `action: { do: "make_it_yours", label: "Make it yours" }`, and a new test checks that the page and the MCP say the same words. I didn't run the whole suite. `tsc`, `gate.ts`, `design-lint` and the tests for this change pass, including `test/explorer-make-it-yours.test.ts`, `test/mcp-size.test.ts`, `test/explore-parity.test.ts` and `test/explore.test.ts`…
shipped production is serving it deploy befa93c
The explore MCP row for each app now carries action: { do: "make_it_yours", label: "Make it yours" }, and a new test checks that the page and the MCP say the same words. I didn't run the whole suite. tsc, gate.ts, design-lint and the tests for this change pass, including test/explorer-make-it-yours.test.ts, test/mcp-size.test.ts, test/explore-parity.test.ts and test/explore.test.ts.
What was already there: the first checklist item was already on main (#10728, commit 49eeb810f). Each app row on /explore already has a one-tap "Make it yours" form with a receipt and undo, and the make_it_yours MCP tool already previews, forks and undoes. What was missing was the MCP row's side.
What changed:
- Shared constant:
src/actions/explore.tsexportsMAKE_IT_YOURS, andExploreRowhas an optionalactionset only when the row is copyable. That is a field on the existingexploretool, not a new tool, and the tool description is unchanged, so MCP size is unaffected. - Page button:
src/web/studio/explore.tstakes its button label from the same constant. - Parity test:
test/explorer-make-it-yours.test.tshas a new test. It checks that theexplorerow'saction.labelappears on the/explorepage, thataction.donames a callable tool, and that calling it doesn't return an error.
Left out: a dry-run preview on the explore row itself. The tool's own preview is one call away.
To try: open /explore?kind=app, then call MCP explore with { q: "kind:app" } and compare each item's action.label with the button.
I had to add a "Try:" line to the commit message to get past the pre-push hook.
🤖 Generated with Claude Code
Small and correct in shape, two things to fix in one round.
- Both doors / docs: the new
actionfield on the explore row is a contract for agents, and docs/features/explore.md does not mention it. Add two lines there: the field, when it is present (exactly whencopyable), and that it is the page button's label and the name of the tool that forks. - The test's comment says the named tool 'previews first', but it only asserts
isErroris falsy. Assert the preview: the result text says it is a plan and that nothing was made, andgetRepo(app.db, 'bob', 'garden')is still undefined after the call. Otherwise a tool that forks on the first call would pass.
Also tidy: copyable(repo) is computed twice in rank(); compute once. Import MAKE_IT_YOURS in sorted position with the other values, not after the type import. Design: no visual change, the button is the same named component, an <a> for guests and a post form for signed-in people; fine.
I addressed all of @juniper's points and pushed. tsc and the explore tests pass (40 in test/explorer-make-it-yours.test.ts and test/explore.test.ts); I did not re-run test/mcp-size.test.ts or design-lint after these changes.
- Docs:
docs/features/explore.mdhas a new section, "Make it yours on an app row". It names theactionfield and says it is present exactly whencopyable. It also sayslabelis the page button's text anddois the name of the tool that forks, which plans unless called withdryRun: false. - Test: the named tool is now asserted to preview. The result has
dryRun: trueandfrom: "alice/garden", there is nosays, andgetRepo(app.db, "bob", "garden")is still undefined afterwards. A tool that forked on its first call would fail this. The plan has no wording that says "plan", so I asserted its shape instead. - Tidy:
copyable(repo)is computed once intocanCopyinsrc/actions/explore.ts.MAKE_IT_YOURSis imported with the other values insrc/web/studio/explore.ts.
The change is the right size and the preview shows the page button. Two test gaps to fix in one round.
- The docs promise
actionis present exactly whencopyable, but no test covers the absent side. Add a row that is not copyable (for example the viewer's own app, or a private one they cannot copy) and assert it has noactionkey. - The page half of the parity test is
page.toContain(row.action.label). That passes if the words appear anywhere on /explore, for instance in the nav or the footer. Assert the label inside the row's owndata-make-it-yoursform or button (or that the page's label comes fromMAKE_IT_YOURS), so changing the page button alone fails it.
The security, design and docs checks are met: the diff adds no write path, the button is the same named component, and the how-to is in docs/features/explore.md, not README.
On /explore, each row that can be copied now has a one-tap "Make it yours" button. In the explore MCP result, the same row carries action: { do: "make_it_yours", label: "Make it yours" }. The page button and the MCP row both read their label from one constant, MAKE_IT_YOURS in src/actions/explore.ts. action is only there when copyable is true. The rule and how to use it are in docs/features/explore.md.
This round answers review and only changes test/explorer-make-it-yours.test.ts:
- The no-action case is now tested. alice asks
explorefor her ownalice/garden. The test checks the row hascopyable: falseand noactionkey at all. bob's row for the same app hascopyable: trueand the action. - The page check is now limited to the row's own button. The test fetches /explore signed in as bob and takes that row's
<form … data-make-it-yours>. It then checks that the<button>inside that form containsrow.action.label. Before, the label could have matched anywhere on the page. To check it can fail, I changed only the page button's label to "Copy it": this test failed, then I put the label back.
The test file (9 tests), bun x tsc --noEmit and bun scripts/gate.ts pass. The test run logs a "could not follow its parent" error because git isn't installed in this sandbox. All 9 tests still pass. No runtime code changed in this round.
🤖 Generated with Claude Code
On /explore, every row someone can copy now has a one-tap "Make it yours" button. The explore MCP row for the same app carries action: { do: "make_it_yours", label: "Make it yours" }. Both read their label from one constant, MAKE_IT_YOURS in src/actions/explore.ts. action is only there when copyable is true. How to use it is in docs/features/explore.md.
test/explorer-make-it-yours.test.ts checks three things:
- bob's row for
alice/gardenhas the action. - alice's row for her own app has
copyable: falseand noactionkey at all. - The label is read from the
<button>inside that row's owndata-make-it-yoursform on bob's /explore, not from anywhere on the page. Changing only the page button's label makes this test fail. - Calling the named
make_it_yourstool only previews the copy and does not make one.
Check fix. I merged main into the branch. One of the three failures, "each return of the public door and of answered is floored" in test/security-floor-doors.test.ts (from #10954), fails on main itself. It looked for the exact text .then(securityFloor) in src/http/server.ts. Since then #10932 and #10974 changed that line to .then((r) => securityFloor(resourcePolicy(request, r))), so the text no longer matches. The test was wrong, not the server: it now accepts either form, and still fails if securityFloor is missing from that line. If main fixes this first, the merge conflict is one line.
The other two failures, "a served request lands in the record under its route pattern…" and "past its deep budget an address gets 429…", passed every time here, before and after the merge. That includes five repeat runs of test/crawl-budget.test.ts and test/perf-server-hook.test.ts, and one of test/shield.test.ts. This branch touches none of their code, so I left them alone. If they fail again, the cause is outside this change.
Those six test files, bun x tsc --noEmit and bun scripts/gate.ts all pass here.
🤖 Generated with Claude Code
On /explore, every app row the viewer can copy has a one-tap "Make it yours" button. The explore MCP row for that app now carries action: { do: "make_it_yours", label: "Make it yours" }. Both take their label from one constant, MAKE_IT_YOURS in src/actions/explore.ts. action is only present when copyable is true. How to use it is in docs/features/explore.md.
test/explorer-make-it-yours.test.ts checks:
- bob's row for
alice/gardencarries the action. - alice's row for her own app has
copyable: falseand noactionkey. - The label is read from the
<button>inside that row'sdata-make-it-yoursform on bob's /explore page, not from the page as a whole. Changing only the page button's label makes this test fail. - The named
make_it_yourstool only previews the copy and does not make one.
The merge conflict was in test/security-floor-doors.test.ts. This branch and main had each fixed the same out-of-date assertion about src/http/server.ts. I kept main's version, so the pull no longer touches that file and changes only the four files above. After the merge, test/security-floor-doors.test.ts and test/explorer-make-it-yours.test.ts pass (16 tests), and so do bun x tsc --noEmit and bun scripts/gate.ts.
🤖 Generated with Claude Code
Both earlier asks are met. The test now covers the absent side: alice's own app has copyable false and no action key. The page check reads the button inside the row's own data-make-it-yours form, and the named tool is asserted to preview only. The change is four files, adds no write path, uses the same btn component and one constant for the words, and the how-to is in docs/features/explore.md, not README. The preview serves the Make it yours button on /explore.
Next, two things I would do:
- Non-blocking: add a keyboard/screen-reader label that names the app, e.g. aria-label "Make alice/garden yours", since a list of identical buttons is ambiguous to a screen reader.
- Non-blocking: carry a dry-run preview on the MCP row later if agents ask for it.
The explore MCP row for each app now carries
action: { do: "make_it_yours", label: "Make it yours" }, and a new test checks that the page and the MCP say the same words. I didn't run the whole suite.tsc,gate.ts,design-lintand the tests for this change pass, includingtest/explorer-make-it-yours.test.ts,test/mcp-size.test.ts,test/explore-parity.test.tsandtest/explore.test.ts.What was already there: the first checklist item was already on main (#10728, commit 49eeb810f). Each app row on
/explorealready has a one-tap "Make it yours" form with a receipt and undo, and themake_it_yoursMCP tool already previews, forks and undoes. What was missing was the MCP row's side.What changed:
src/actions/explore.tsexportsMAKE_IT_YOURS, andExploreRowhas an optionalactionset only when the row is copyable. That is a field on the existingexploretool, not a new tool, and the tool description is unchanged, so MCP size is unaffected.src/web/studio/explore.tstakes its button label from the same constant.test/explorer-make-it-yours.test.tshas a new test. It checks that theexplorerow'saction.labelappears on the/explorepage, thataction.donames a callable tool, and that calling it doesn't return an error.Left out: a dry-run preview on the explore row itself. The tool's own preview is one call away.
To try: open
/explore?kind=app, then call MCPexplorewith{ q: "kind:app" }and compare each item'saction.labelwith the button.I had to add a "Try:" line to the commit message to get past the pre-push hook.
🤖 Generated with Claude Code
Closes #10951