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

The `map` tool's answer draws a ui://forge/map card: ready now, being worked on, waiting, by pillar #4178

Merged opened this

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

Merged

The `map` tool's answer now draws a "Plan" card at `ui://forge/map`. The branch is rebuilt on current main, and `test/card-shell.test.ts` and `test/mcp.test.ts` pass (42 tests). I did not run the type check: `tsc` could not find the `bun` type definitions here, and it only prints that error. The forge runs the full check on this push.

shipped production is serving it deploy 2e641a3

opened this

The map tool's answer now draws a "Plan" card at ui://forge/map. The branch is rebuilt on current main, and test/card-shell.test.ts and test/mcp.test.ts pass (42 tests). I did not run the type check: tsc could not find the bun type definitions here, and it only prints that error. The forge runs the full check on this push.

  • Card: map is added to CARD_KINDS, CARD_OF_TOOL and the card titles in src/web/studio/cards.ts.
  • Rows and status: There is one row per pillar (name, ready, working, waiting). The first five ready issues follow as rows linking to their pages. The status line reads "N ready · M working · K waiting".
  • Data and escaping: Only what the tool already returns is used. The card page escapes every value, as for the other kinds.
  • Docs: docs/MCP.md describes the new card.
  • Tests: test/mcp.test.ts lists ui://forge/map among the resources and checks that the map descriptor points at it. test/card-shell.test.ts draws a seeded plan and checks the rows. Both fail without the change.
  • Also on the branch:

- I shortened the issue and map tool descriptions in src/http/mcp.ts to keep tools/list under 40 KB. - Card links now open with target="_blank" and rel="noopener noreferrer". That change was already on the branch, not something I did this round.

The card has no actions. To try it, call map with an app from a harness that shows MCP Apps.

Closes #3343

commented

The map tool's answer now draws a "Plan" card at ui://forge/map. The branch is rebuilt on current main, and test/card-shell.test.ts and test/mcp.test.ts pass (42 tests). I did not run the type check: tsc could not find the bun type definitions here, and it only prints that error. The forge runs the full check on this push.

  • Card: map is added to CARD_KINDS, CARD_OF_TOOL and the card titles in src/web/studio/cards.ts.
  • Rows and status: There is one row per pillar (name, ready, working, waiting). The first five ready issues follow as rows linking to their pages. The status line reads "N ready · M working · K waiting".
  • Data and escaping: Only what the tool already returns is used. The card page escapes every value, as for the other kinds.
  • Docs: docs/MCP.md describes the new card.
  • Tests: test/mcp.test.ts lists ui://forge/map among the resources and checks that the map descriptor points at it. test/card-shell.test.ts draws a seeded plan and checks the rows. Both fail without the change.
  • Also on the branch:

- I shortened the issue and map tool descriptions in src/http/mcp.ts to keep tools/list under 40 KB. - Card links now open with target="_blank" and rel="noopener noreferrer". That change was already on the branch, not something I did this round.

The card has no actions. To try it, call map with an app from a harness that shows MCP Apps.

Approved — before the latest push

Approved. I read every changed line, applied the head's diff to a clean copy of main and ran test/card-shell, test/mcp, test/mcp-size and test/mcp-pulls: 50 pass, 0 fail, and tools/list stays under the 40 KB ceiling, which is why the issue/map descriptions were trimmed.

Security: the tool is read-only and keeps the existing planRepo + sealedScope gate, and the card only reshapes what map already returns to that caller. Values reach the sandboxed shell as textContent. Links are built only when origin is http(s), from the repo's owner/name slugs and an integer number. The shell still rejects any non-http(s) link, and both of those are tested (javascript: stays text). Adding target=_blank and rel=noopener noreferrer does no harm because the click is still handed to the host through ui/open-link.

The counts are consistent: per-pillar ready and blocked come from pillarRows, and "working" counts only open issues the crew is marked on, overall and per pillar. A repo without pillars gets the status line and the ready rows, with no pillar rows.

Preview: / answers 200, and /mcp refuses requests without a token as before. I did not send a forge token to the preview host, so I judged the card from the tests rather than a live MCP host.

I found no design-system or terminal concerns: this is the MCP card model, not a page, and it adds no action.

commented

I merged current main into the branch and pushed it. The merge now applies cleanly. I ran test/card-shell.test.ts and test/mcp.test.ts (42 tests, all pass) but not the whole suite or the type check, which the forge runs on this push.

There was one conflict, in src/http/mcp.ts, on the issue tool's description. I kept the shorter wording from main, so my earlier trim of that description is gone. The feature itself is unchanged:

  • Plan card: The map tool's answer draws the ui://forge/map card, titled "Plan".
  • Rows: There is one row per pillar (ready, working, waiting) and the first five ready issues as links.
  • Status line: It reads "N ready · M working · K waiting".
  • Tests: The new tests are in test/card-shell.test.ts and test/mcp.test.ts.
  • Docs: docs/MCP.md describes the card.

To try it, call map with an app from a harness that shows MCP Apps.

Changes requested — before the latest push

The wiring is right: map is in CARD_KINDS, CARD_OF_TOOL and the titles, there is a resource test, the docs are updated, the row values are drawn with textContent only, and links are only made for http(s). The card's numbers are wrong, though. It is supposed to show three separate groups (ready now, being worked on, waiting), but it counts the same issue more than once, and the headline disagrees with the pillar rows below it.

Fix first: take every count from one source, the word mapJson already gives each issue. The status line uses ready.length (readyNow by the graph, which includes issues the crew is on, held, parked or deferred) and blockedOn.length. The pillar rows use p.ready/p.blocked, which pillarRows takes from wordFor, where working outranks ready (src/web/pages/map.ts:552). With one crew run on a ready issue, the headline says "6 ready · 1 working" and the pillar row says "5 ready · 1 working". The same issue also shows up in the 'ready' links. Use v.counts.ready / v.counts.working / v.counts.blocked (or count issues by word) for the status line. Count each pillar's working issues by word === "working" among its issues. Take the five links from ready filtered to word === "ready", so nothing the crew is already building is offered as ready.

Then fix the test fixture. It encodes the contradiction: #3 is both ready and working, and P0 claims 6 ready plus 1 working out of 6 open. Build the fixture with word set as mapJson would. Even better, build it from a real mapJson(graph, marks, pillars) so the card is tested against what the tool actually returns.

Smaller points: the per-pillar working count also counts structural umbrella issues and the direction issue, which pillarRows skips, so going by word with the same exclusions is consistent. The new target=_blank is not asserted in the shell test.

Security is clean: the tool is read-only, the graph is already limited by sealedScope, output is drawn with textContent, and the link scheme is checked both where the card is built and in the shell.

commented

I fixed the Plan card so each issue is counted once, and pushed the branch. test/card-shell.test.ts and test/mcp.test.ts pass (42 tests); I did not run the type check.

  • Counts: The status line and the pillar rows now both take their numbers from the word that mapJson gives each issue. Before, the headline used ready.length and blockedOn.length while the rows used wordFor, so an issue the crew was on was counted as ready in the headline and as working in its row.
  • Working per pillar: A pillar's working count is the open issues in that pillar with word === "working".
  • Five links: They come from ready filtered to word === "ready", so nothing the crew is building, or that is held or parked, is offered as ready.
  • Test: The fixture is now built from a real mapJson(...) call. It has one umbrella, one issue the crew is on, one held, one waiting and one closed. It asserts the rows, the five links and that the headline equals the sum of the pillar rows. The shell test now also asserts target="_blank".

Where I departed from your suggestion: I did not use v.counts for the status line. counts includes the pillar umbrellas, which pillarRows skips. In the fixture that gave "2 waiting" in the headline against 1 in the rows. Instead I count open issues by word, leaving out the issues whose number is a pillar's umbrella, so the headline equals the rows' sum.

Not covered: The direction issue is not excluded, because mapJson does not expose which issue it is. The Later umbrella and the gate are also not excluded, because pillars rows carry umbrella only for named pillars. Say if you want mapJson to expose those.

Approved

Approved. The map tool's answer now draws a Plan card. It uses only data the tool already returns. Titles reach the shell through textContent. Links are built only when origin is http(s) and the shell checks the scheme again. The new anchor gets rel="noopener noreferrer". The card has no actions, so no new write path needs a check on who is asking. The two new tests are built from a real mapJson call and would fail without the change. test/card-shell.test.ts and test/mcp.test.ts pass at b7c59684 (42 tests). This is only the MCP card, so the page and src/tty.ts don't change.

The preview's /mcp needs a token, so I checked the change through the tests and not by calling the tool live.

One low-severity follow-up is on the line below. It doesn't block this pull.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs