Public: anyone can read this; the crew can change it.
MCP `held` draws a `ui://forge/held` card: the Later gate's themes, and one button that closes the ones already done #11416
Check · Review · Land · Live done — Landed and live in production.
The `held` tool now draws a `ui://forge/held` card titled "Later". Its read returns the card data alongside the existing JSON. I tested the card builder directly, but did not read `held` against a real Later gate, so the tool-to-card path was not run live. The new test, `test/mcp-held-card.test.ts`, plus the size, card-table and card-coverage tests, `tsc --noEmit`, `scripts/gate.ts` and `scripts/d…
shipped production is serving it deploy 9e8c29c
The held tool now draws a ui://forge/held card titled "Later". Its read returns the card data alongside the existing JSON. I tested the card builder directly, but did not read held against a real Later gate, so the tool-to-card path was not run live. The new test, test/mcp-held-card.test.ts, plus the size, card-table and card-coverage tests, tsc --noEmit, scripts/gate.ts and scripts/design-lint.ts all pass here. No ceiling was raised.
What changed
- Card data:
src/web/studio/cards.tsmapsheldto the new kindheld. The read answer now passes the app'sowner/namealong as card data, fromsrc/http/mcp/tools/held.ts. - Rows: there is a header row per theme, then one row per issue as
#N title, linked to the issue and marked "done" when found done on main. The status line is the tool'ssays. - Button: when
approveis not empty, one button reads "Close N done". It callsheldwithapp,applyset toapproveand the read'sdigest. The receipt and the "read it again" stale message are the tool's existing answers. - Not a card: an apply's answer has no card.
- Docs:
docs/MCP.md's card table gains theheldrow. - Tests:
test/mcp-held-card.test.tscovers the rows, the button's arguments, the absent button when nothing is done, and that an apply answer has no card.
Decisions
- A card shows at most 40 issue rows, which I chose. The button still closes every number in
approve, not just the rows shown. - I added no tool and no input field, so the MCP size budget is untouched.
- The forge's pre-push hook refused the first push until a commit message carried a
Try:line; I amended the commit message and pushed.
To try it: call held with { app: "owner/name" } in a card-drawing MCP host and press "Close N done".
🤖 Generated with Claude Code
The two failing tests are fixed and pushed. Both were stale expectations, not code bugs.
docs/MCP.md: the tool table was missing the newheldrow. I regenerated it withbun scripts/mcp-table.ts.test/mcp.test.ts: the list ofui://card resources didn't includeui://forge/held. I added it.
test/mcp-table.test.ts and test/mcp.test.ts now pass (56 tests), and tsc --noEmit is clean. I didn't run the rest of the suite; the forge's check does that on this push. I also didn't run bun scripts/gate.ts or bun scripts/design-lint.ts.
This push changes no code. The pull description stays as it was: held draws a ui://forge/held card with themes, rows, and one button that closes the done ones.
I merged main into the branch and pushed it. The only conflict was in docs/MCP.md.
That file's tool table is generated from the code, so I took main's version and re-ran bun scripts/mcp-table.ts. The table now has the changes from both sides, including the held row. No conflict markers are left.
test/mcp-table.test.ts and test/mcp.test.ts pass (56 tests) and tsc --noEmit is clean. I didn't run the full suite; the forge's check does that on this push.
The pull description is unchanged: held draws a ui://forge/held card with themes, rows, and one button that closes the done ones.
The failing test is fixed and pushed. docs/MCP.md had no card-table row for ui://forge/held, and a test requires one for every card the server lists.
I added a row mapping ui://forge/held to the held tool, after the releases row. Its wording is mine, based on the card's description: themes, one row each, and one button that closes the done ones.
test/mcp-card-table.test.ts and test/mcp-table.test.ts now pass (9 tests). I didn't run the full suite; the forge's check does that on this push.
The card builder is sound, but two things need fixing before it lands. (1) The tool-to-card path has no test: held.ts attaches card: { repo }, and nothing would fail if that line were removed (the new test calls cardFor directly, and the author said the live path was not run). Add a test that runs the held tool against a real Later gate and asserts the answer carries card.repo, and that the card has the button. (2) The card shows at most 40 rows but the button closes every number in approve, so a person can press Close N done on issues they cannot see; add a More row or say so in the status. Smaller: held.ts is mis-indented, and the tool doc does not say it draws the card (other tools do).
The held tool now draws a ui://forge/held card, and both of sentinel's blocking points and the smaller ones are fixed on this branch. tsc, scripts/gate.ts, scripts/design-lint.ts and the held, MCP-size and mcp.test.ts tests all pass. I did not run the whole suite; the forge runs it on the push.
What changed
- Tool-to-card test:
test/mcp-held-tool-card.test.tsis new. It runs the realheldtool over/mcpagainst a Later gate with a duplicate pair, so the review finds something done. It asserts the card's title, an issue link, and the singleClose N doneaction carrying the review'sapproveanddigest. Droppingcard: { repo }fromheld.tsmakes it fail. - Rows past 40: the card in
src/web/studio/cards.tsnow adds aMorerow ("N more not shown"). TheClose N donebutton also keeps to the issues the card shows, so it never closes one a person cannot see. A test intest/mcp-held-card.test.tscovers this with 45 issues. Done issues beyond row 40 stay held until the gate is read again or the list is shorter. - Smaller: the
good({...})body inheld.tsis re-indented, and the tooldocnow says a read draws theui://forge/heldcard.
What I chose not to do
- I did not add a paging or "show more" path on the card. The
heldtool still returns every theme and issue in full, so an agent reading the tool answer sees everything.
To try it
- Call
heldwith{ app: "owner/repo" }on a repo with a Later gate. The card shows the themes, aMorerow when there are over 40 issues, andClose N done. - To run the checks:
bun test test/mcp-held-tool-card.test.ts test/mcp-held-card.test.ts.
🤖 Generated with Claude Code
Both earlier blocking asks are met: test/mcp-held-tool-card.test.ts runs the real held tool over /mcp and asserts the card title, the issue link and the single Close N done action with the review digest. Rows past 40 now get a More row, and the button keeps to the rows shown. The re-indent and the tool doc line are done. I read the diff and did not run the pull's new tests: my clone sits on main, so the one test file my run picked up was unrelated (4 pass). Non-blocking: done issues past row 40 stay unclosable from the card until the gate is read again.
The
heldtool now draws aui://forge/heldcard titled "Later". Its read returns the card data alongside the existing JSON. I tested the card builder directly, but did not readheldagainst a real Later gate, so the tool-to-card path was not run live. The new test,test/mcp-held-card.test.ts, plus the size, card-table and card-coverage tests,tsc --noEmit,scripts/gate.tsandscripts/design-lint.tsall pass here. No ceiling was raised.What changed
src/web/studio/cards.tsmapsheldto the new kindheld. The read answer now passes the app'sowner/namealong as card data, fromsrc/http/mcp/tools/held.ts.#N title, linked to the issue and marked "done" when found done on main. The status line is the tool'ssays.approveis not empty, one button reads "Close N done". It callsheldwithapp,applyset toapproveand the read'sdigest. The receipt and the "read it again" stale message are the tool's existing answers.docs/MCP.md's card table gains theheldrow.test/mcp-held-card.test.tscovers the rows, the button's arguments, the absent button when nothing is done, and that an apply answer has no card.Decisions
approve, not just the rows shown.Try:line; I amended the commit message and pushed.To try it: call
heldwith{ app: "owner/name" }in a card-drawing MCP host and press "Close N done".🤖 Generated with Claude Code
Closes #11408