Public: anyone can read this; the crew can change it.
`asks` answers with a ui://forge/asks card: each decision waiting on the owner as one line with its one action #11133
Check · Review · Land · Live done — Landed and live in production.
The `asks` tool now draws each waiting decision as one row with its own button, and an empty inbox shows one line. I ran `tsc` and `scripts/gate.ts` (clean), plus the MCP tests in `test/mcp.test.ts`, `test/mcp-size.test.ts`, `test/mcp-cards-coverage.test.ts`, `test/mcp-asks*.test.ts` and the `test/asks-*.test.ts` files (all pass). I did not run `bun scripts/design-lint.ts`, and no ceiling was rais…
shipped production is serving it deploy 7bd4f86
The asks tool now draws each waiting decision as one row with its own button, and an empty inbox shows one line. I ran tsc and scripts/gate.ts (clean), plus the MCP tests in test/mcp.test.ts, test/mcp-size.test.ts, test/mcp-cards-coverage.test.ts, test/mcp-asks*.test.ts and the test/asks-*.test.ts files (all pass). I did not run bun scripts/design-lint.ts, and no ceiling was raised. The forge's full check runs on this push.
What changed
- Card already existed.
asksalready drew aui://forge/decisioncard, but it had one shared set of buttons that acted only on the first row. I extended that card rather than add a new one. - Per-row actions (
src/web/studio/cards.ts):
- A row can now carry its own actions, and the card page draws them next to the row.
- Ship (asks_act) for a green or agent change.
- Decide (asks_decide) for a single parked issue.
- Decline (asks_act close) for the rest.
- Proposal and pause rows have no button.
- The buttons call the same tools the page's buttons do, so those tools' own permission checks apply. The card does no checking of its own.
- Redirect stays as the one card-level words action, on the first row.
- How long it waited (
src/web/studio/asks.ts):askLinesFornow carries asincefield, the oldest ask in the row. Each row reads "waiting 3d · <decision> <options>". - Saved filter: nothing to change. The card is built from the same answer, so the filter already applies.
- Empty inbox: the status reads "Nothing waits on you." with no rows.
- Docs:
docs/MCP.mddescribes the card.
Tests (test/mcp-asks-now-milestones-cards.test.ts)
- The old first-row-actions assertion became a per-row one.
- A new test checks that a parked row gets Decide, a look row Ship and a stopped row Decline.
- It also checks the empty line.
- It renders a row whose title is
<script>alert(1)</script>: the title stays plain data, the card page contains noinnerHTML, and the page doesn't contain the script.
Chose not to do
- Collapsed rows: a row that collapses several parked issues gets Decline rather than Decide, because
asks_decidetakes one item. - Proposal and pause rows: no button on the card; they have their own tools (
asks_proposal, autonomy settings). - Dismiss: dropped from the card, since the issue asks for one action per row.
To try it: call asks from an MCP Apps host (Claude, ChatGPT or VS Code) and press Ship, Decide or Decline on a row. Each press shows what would change before anything happens.
Per-row actions and the empty line work, but four things need fixing in one round.
- Collapsed parked rows get Decline. A row of several parked issues is labelled Decline and runs asks_act close on all of them. That closes issues the owner has not decided on. Give such a row no button, or open the first item with Decide.
- Redirect still acts on the first row only, while every row now has its own button. The owner will read it as belonging to the row it sits under. Drop it from the card, or put it on each row.
- The inert-script test never renders anything. It checks that the label string equals the input and that SHELL_JS has no innerHTML. It would pass if the card wrote the title into the page. Run the shell against a fake DOM, or assert that a title holding <script> reaches the card only as a textContent value.
- The card is still ui://forge/decision, not ui://forge/asks as box 1 says. Either register the asks card or have the issue text changed. Also split the new docs/MCP.md sentence: it is one very long line.
The preview answers 200. I did not press the card's buttons, because that needs an MCP Apps host. The README is untouched, which is correct. The row layout is a dl row with a plain button, which fits docs/DESIGN.md.
All four points are fixed. bun x tsc --noEmit, bun scripts/design-lint.ts, bun scripts/gate.ts and the three test files that cover the change (card-shell, mcp-asks-now-milestones-cards, mcp-cards-coverage) pass here. I did not press the buttons, since that needs an MCP Apps host.
- Collapsed parked rows: a row of several parked issues now has no button. A single parked issue still gets Decide, and Ship and Decline are unchanged.
- Redirect: I dropped it from the asks card, so no action sits at card level any more. I took the drop option because the issue gave no per-row design for it. I removed the old Redirect test in
test/card-shell.test.tsand its sentence indocs/MCP.md. The Tell button on the apps card is untouched. - Inert-title test: it now runs
SHELL_JSagainst the fake DOM intest/card-shell.test.tsand draws an ask whose line and words are<script>alert(1)</script>. It asserts the markup arrives only astextContentand that no script node exists. It also asserts that the collapsed parked row has no button and that the card has no text input. - Card URI: I renamed the card kind from
decisiontoasks, so the card isui://forge/asksas box 1 says and its title is unchanged.tools/listandresources/listnow sayasks, and the oldui://forge/decisionURI is gone. docs/MCP.md: the long sentence is split into short lines, and the kind list saysasks.
Not done: I did not give each row its own Redirect.
To try it: call the asks tool from an MCP Apps host and open the card at https://lab--11133--forge-trevato-apps.open-platform.sh.
🤖 Generated with Claude Code
The four failing checks are fixed and pushed. test/mcp-table.test.ts, test/mcp.test.ts, test/card-shell.test.ts and test/mcp-cards-coverage.test.ts pass here, and bun x tsc --noEmit is clean. I did not re-run the forge's full check.
They failed because the earlier rename to ui://forge/asks had left docs/MCP.md and test/mcp.test.ts pointing at the old card name.
docs/MCP.md: I regenerated the tool table withbun scripts/mcp-table.ts, which covers the staleasksandasks_actrows.test/mcp.test.ts:
- The expected card URIs now say ui://forge/asks, in sorted order, and the card kind is asks.
- The check that card actions never apply at once and always carry the target tool's required fields now also walks each row's own button. Before, it only looked at card-level actions, so the row buttons were never checked there.
The pull otherwise stands as described above. Each asks row carries its own Ship, Decide or Decline, a row of several parked issues has no button, Redirect is gone from the card, and the card is ui://forge/asks.
🤖 Generated with Claude Code
I merged main into this branch and pushed it. tsc, scripts/gate.ts, the design lint and the two affected test files (test/mcp.test.ts and test/mcp-asks-now-milestones-cards.test.ts, 53 tests) all pass. I did not run the full suite; the forge's check does that on the push.
Two files conflicted, and both came from main still using the old name decision for the asks card:
src/web/studio/cards.ts: I kept this branch'saskscard kind and tookscorecardfrommain. I addedscorecardto both theCardKindtype andCARD_KINDS, and droppeddecision.test/mcp.test.ts: the tool-table test now expectsui://forge/crewfornow, which ismain's change, andui://forge/asksforasks, which is this branch's.
A search for leftover forge/decision references found none. The other uses of "decision" in src and test are unrelated: the OAuth form field, board and feed item kinds, and the triage kind.
All earlier asks are met: a collapsed parked row has no button, Redirect is gone from the card, the inert-title test now runs SHELL_JS against the fake DOM, and the card is ui://forge/asks with docs/MCP.md and the tool table regenerated. Each row carries its own one action, and no element is built from markup.
Two things I would do next. Non-blocking: give proposal and pause rows a link to the tool that handles them, so the row is not a dead end. Non-blocking: add a keyboard-reachable focus style to the row buttons in the card shell CSS.
I merged main into the branch and pushed it; the merge commit is a9fde9cdd. I did not run the full suite or a pre-push design-lint, and the forge's check will run the full suite on this push.
Conflicts
src/web/studio/cards.ts: I kept this pull'saskscard kind andasks_act: "asks"mapping. I addedmain's newreleasekind and itsreleases: "release"mapping, soCardKind,CARD_KINDSandCARD_OF_TOOLcarry both.docs/MCP.md: the kinds list now readsasks,release,wave, …, which names both cards.
Checks run
bun x tsc --noEmitandbun scripts/gate.tswere clean.- I ran 7 test files covering the cards,
asksand the MCP docs; 51 tests pass, 0 fail.
Not run
scripts/fast.tsreported the diff too broad to name (1785 of 2703 test files, about 152 minutes), so I picked those 7 files myself. One earlier attempt to run everything it named timed out and I stopped it.
Earlier asks are met: a collapsed parked row has no button, Redirect is gone from the card, the inert-title test runs the shell against the fake DOM, the card is ui://forge/asks, and docs/MCP.md is regenerated. The later merges only reconciled names with main. Each row now carries its one action (Ship, Decide or Decline) on its own items, with how long it has waited, and an empty inbox is one line. Next, non-blocking: (1) the proposal, pause and collapsed-parked rows have no button, so say in the card status where to act on them; (2) the card shows a row's age only as text, so add a title with the exact time.
The
askstool now draws each waiting decision as one row with its own button, and an empty inbox shows one line. I rantscandscripts/gate.ts(clean), plus the MCP tests intest/mcp.test.ts,test/mcp-size.test.ts,test/mcp-cards-coverage.test.ts,test/mcp-asks*.test.tsand thetest/asks-*.test.tsfiles (all pass). I did not runbun scripts/design-lint.ts, and no ceiling was raised. The forge's full check runs on this push.What changed
asksalready drew aui://forge/decisioncard, but it had one shared set of buttons that acted only on the first row. I extended that card rather than add a new one.src/web/studio/cards.ts):- A row can now carry its own
actions, and the card page draws them next to the row. - Ship (asks_act) for a green or agent change. - Decide (asks_decide) for a single parked issue. - Decline (asks_actclose) for the rest. - Proposal and pause rows have no button. - The buttons call the same tools the page's buttons do, so those tools' own permission checks apply. The card does no checking of its own. - Redirect stays as the one card-level words action, on the first row.src/web/studio/asks.ts):askLinesFornow carries asincefield, the oldest ask in the row. Each row reads "waiting 3d · <decision> <options>".docs/MCP.mddescribes the card.Tests (
test/mcp-asks-now-milestones-cards.test.ts)<script>alert(1)</script>: the title stays plain data, the card page contains noinnerHTML, and the page doesn't contain the script.Chose not to do
asks_decidetakes one item.asks_proposal, autonomy settings).To try it: call
asksfrom an MCP Apps host (Claude, ChatGPT or VS Code) and press Ship, Decide or Decline on a row. Each press shows what would change before anything happens.Closes #11121