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

The MCP answers the decisions waiting for the owner as a ui:// card with ship and decline on it #11852

Merged opened this

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

Merged

The `asks` card already had Ship, Decline and Decide buttons, but pressing one only ran a dry run. The card never showed the page's confirm step, so nothing could be settled from it. This pull adds that step.

shipped production is serving it deploy 8f18665

opened this

The asks card already had Ship, Decline and Decide buttons, but pressing one only ran a dry run. The card never showed the page's confirm step, so nothing could be settled from it. This pull adds that step.

What changed

  • Plan card: pressing Ship or Decline now returns a plan card titled "Confirm". It lists each ask as before → after · outcome, and its status line reads like the page's header, for example Close: 1 of 1 · 0 refused or skipped · undo afterwards.
  • Confirm button: the plan card carries one button, such as "Close 1" (the page's own wording). It re-calls asks_act with dryRun:false, which returns the same receipt sentence and Undo card as before.
  • Where the code is: the plan card is built in src/web/studio/cards.ts. src/http/mcp/tools/asks_act.ts now hands the card the action, items and reason on a dry run. The ui://forge/asks row in docs/MCP.md is updated.
  • Token scope: the buttons call the same tools, so the token's scope still decides what can be done. A read-only token can plan but its confirm is refused; the test checks this.
  • Test: test/mcp-asks-card-decide.test.ts lists asks, presses Decline, checks nothing changed, confirms, checks the receipt, and checks the page shows the ask before and not after.

What I did not do

  • I added no tool, argument or schema field, so tools/list does not grow.
  • I did not change the shared card script; the plan card is plain data drawn the same escaped way as the others.
  • The test shows the ask gone from /asks, but does not fetch /asks?op=… to check the receipt banner.

Checks: the gate and tsc are clean. I ran only mcp-size, the cards tests and the new test (all pass), not the full suite.

To try it: call the asks tool and press Decline on an assigned ask. Check the plan card, then press "Close 1". The first push was refused by a pre-push hook that wants a Try: line in a commit message, so there is an empty commit carrying it.

🤖 Generated with Claude Code

Closes #11816

commented

The asks card already had Ship, Decline and Decide buttons, but pressing one only ran a dry run. The card never showed the page's confirm step, so nothing could be settled from it. This pull adds that step.

What changed

  • Plan card: pressing Ship or Decline now returns a plan card titled "Confirm". It lists each ask as before → after · outcome, and its status line reads like the page's header, for example Close: 1 of 1 · 0 refused or skipped · undo afterwards.
  • Confirm button: the plan card carries one button, such as "Close 1" (the page's own wording). It re-calls asks_act with dryRun:false, which returns the same receipt sentence and Undo card as before.
  • Where the code is: the plan card is built in src/web/studio/cards.ts. src/http/mcp/tools/asks_act.ts now hands the card the action, items and reason on a dry run. The ui://forge/asks row in docs/MCP.md is updated.
  • Token scope: the buttons call the same tools, so the token's scope still decides what can be done. A read-only token can plan but its confirm is refused; the test checks this.
  • Test: test/mcp-asks-card-decide.test.ts lists asks, presses Decline, checks nothing changed, confirms, checks the receipt, and checks the page shows the ask before and not after.

What I did not do

  • I added no tool, argument or schema field, so tools/list does not grow.
  • I did not change the shared card script; the plan card is plain data drawn the same escaped way as the others.
  • The test shows the ask gone from /asks, but does not fetch /asks?op=… to check the receipt banner.

Checks: the gate and tsc are clean. I ran only mcp-size, the cards tests and the new test (all pass), not the full suite.

To try it: call the asks tool and press Decline on an assigned ask. Check the plan card, then press "Close 1". The first push was refused by a pre-push hook that wants a Try: line in a commit message, so there is an empty commit carrying it.

🤖 Generated with Claude Code

Changes requested — before the latest push

The shape is right and nothing security-relevant slipped: the confirm re-calls asks_act, so the token's scope still decides, and no tool, argument or schema field was added. One round of changes, all tests: the new behaviour is only covered on the happy path.

  1. Test the plan with a reason: close needs one, and the Confirm args must carry it as reason (cards.ts reason spread).
  2. Test a plan where nothing changes (all refused or skipped): the card must show the skipped rows and the refused or skipped count, and carry NO button.
  3. Test that a plan with ok:false still returns the error and no card.
  4. Cap items in the confirm action to what asks_act accepts (100), so the card cannot hand back an action the tool refuses.
  5. Box 1's wording is 'the page's words', but the test never checks the card's rows against the page's text for the same ask; assert one.

Run bun scripts/design-lint.ts and say so in the pull, since src/web/ is touched.

commented

I've addressed all five review points and pushed to the same branch.

Code (src/web/studio/cards.ts)

  • The Confirm button's items now drop empty strings and are sliced to 100, the limit asks_act accepts.
  • A missing counts.refused now counts as 0, so the status no longer prints NaN refused or skipped.

Tests (test/mcp-asks-card-decide.test.ts)

  • Reason: a close plan with a reason produces Confirm args that carry reason.
  • Nothing changes: a plan with only skipped items shows the skipped rows and a non-zero refused or skipped count, and has no button.
  • ok:false: a refused plan returns the error and no card.
  • Cap: 150 items, some empty, come back as 100 non-empty items.
  • Missing count: a plan without counts.refused prints "0 refused or skipped".
  • Page's words: the card row for an ask is checked against the /asks page for the same ask. The card's row label has the ask's title and "assigned to you", and the page has both too. The two don't use the same sentence, so the test compares those two parts rather than the full text.

Checks

  • bun scripts/design-lint.ts is clean: no colour, size or component added outside the system.
  • bun scripts/gate.ts and bun x tsc --noEmit printed nothing, which I took as clean.
  • I ran the card and mcp-asks test files: 287 pass, 2 skipped, 0 fail.
  • scripts/fast.ts said the diff was too broad to name (1658 test files, about 141 minutes), so I picked those files myself.
Approved

The earlier asks are met: empty items are dropped and capped at 100, a missing refused count no longer prints NaN, and the tests cover reason, nothing-changes, ok:false, the cap and the page words. Pressing Decline plans and returns the page confirm card ("Close 1"). Confirming goes through the same asks_act, so token scope still decides, and the test shows a read-only token is refused. No schema or tool was added, so tools/list does not grow. Card text goes through the existing escaped card drawing. The pull adds nothing to README and no new dependency. I could not diff against 0062f7772599 because the clone does not have that commit, so I read the whole current change instead.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs