Introducing Maple: Visual Review Written by People, Fixed by Agents

A coding agent can fix almost anything you hand it — as long as what you handed it is clear and actionable. A visual review comment usually isn’t.

Here’s the loop I got tired of.

Someone opens a preview deployment — a designer, a product manager, whoever has the link — and something is wrong. They screenshot it, drop it in Slack, and write “the spacing under the chart looks off on the dashboard”. Now the work starts: I read the message, open the preview, guess which of four nearly-identical cards they meant, grep for the component, and only then make a two-line change.

The fix was never the expensive part. The translation was. And it is exactly the part a coding agent cannot do for me: it has the words and none of the pointing.

So I built Maple.

The Maple overlay on a preview deployment of maple-kit.org. A reviewer has selected the phrase 'written for people and read by agents' in the headline and is typing a comment on it, with the viewport width recorded beside the comment. Numbered leaf pins sit on the demo page below.
maple-kit.org reviewing its own preview deployment.

What it is

Maple is an open-source overlay that mounts into your app on preview builds. A reviewer clicks the thing that is wrong — an element, a region, a run of text — and says what is wrong with it. Maple records where they pointed, what they were looking at, and who they are, then:

  • hands it to a coding agent over MCP, anchored to file:line;
  • holds the merge until every comment is resolved.

It ships as six packages, all Apache-2.0, all on npm at 0.6.0, with no hosted service anywhere in the path:

  • @maple-kit/core — the server SDK, the overlay and the connector contracts.
  • @maple-kit/mcp — the MCP server an agent talks to.
  • @maple-kit/cli — the maple command.
  • @maple-kit/react — bindings for the reviewer controller.
  • @maple-kit/ui — the composed reviewer parts: the marks, the island, the composer.
  • @maple-kit/classifier — model-backed scoring for the assist tier.

Why I built this myself

There is good work in this space already, and I went looking for it first.

Agentation is the closest thing in spirit and genuinely nice to use — you point at an element and your agent hears about it. It runs against localhost, which is the catch: the only person who can leave a comment is the person running the build. That is the engineer — and Maple is a collaboration experience, like most good work is.

Then there is the commercial tier: Vercel Toolbar puts comments on a deployment, and Pincushion, Chromatic, BugHerd and Marker.io all collect visual feedback well. Each of them does some of what I wanted. None of them does all four:

  • Deployed previews, not localhost. Maple runs on the preview URL your CI already builds, so anyone holding the link can comment.
  • A merge gate. CI blocks while a visual comment is open, and the check is real and named: maple/visual-review.
  • An agent loop. The agent reads the comments, makes the change, and resolves them — the gate clears because the work is actually done.
  • Open source. Apache-2.0, no account in the loop, nothing to migrate off later.

What the agent actually receives

Not a screenshot and a sentence. A comment carries the anchor, the viewport width at pick time, the theme, which disclosure elements were open, who wrote it and how strongly that identity is attested, plus a screenshot taken automatically when the pick commits.

Maple's comment context panel: the comment text, who wrote it with an attestation note, its status, and a block recording the viewport width, the covered width, the theme and which section was open, plus the screenshot taken at pick time.
Everything captured alongside one comment. The identity line says how much to trust itself, which matters more than it sounds.

The agent’s half of the loop is four MCP tools:

ToolWhat it is for
list_commentsEverything on a branch, newest first.
wait_for_commentsBlock until something new arrives.
get_comment_contextEverything needed to act on one comment.
resolve_commentMark one addressed, naming the commit.

The gate is the wedge

Every other tool in this space collects visual feedback and then trusts a human to remember it. Maple posts a maple/visual-review check and blocks the merge while a comment is unaddressed.

The decision is vendor-neutral and lives in core/gate; saying it to a forge is a connector’s job, because GitHub has check runs, GitLab has external status checks, and Bitbucket’s enforcement is Premium-only — three APIs over one decision.

import { decideGate } from "@maple-kit/core/gate";

const { comments } = await store.list({ branch });
const verdict = decideGate(comments, { statusTracked: supports(store, "setStatus") });

Maple stores nothing

No database, no bucket, no account. A connector is one file implementing plain Promise-returning methods — no registration step, no base class:

import type { StoreConnector } from "@maple-kit/core/connectors";

export function myStore(options: MyOptions): StoreConnector {
  return {
    name: "my-store",
    async list(query) {
      /* … */
    },
    async append(comment) {
      /* … */
    },
  };
}

There are connectors for the comment store, media, identity, observability and the forge’s status check. A connector’s capabilities are exactly the methods it defines — there is no second field declaring what it supports, because two sources of truth eventually disagree and then you are debugging a lie. capabilitiesOf reads the object. A store with no setStatus keeps status client-side and the gate reports neutral instead of blocking.

The default store is a GitHub pull request comment, so a fresh install needs nothing you do not already have.

Where it is

In beta, and already usable. Maple is published and still being built, in the open.

If you try it, I would love to hear what broke and what you wished it did instead. Contributions are very welcome — issues, connectors, or an argument about a decision I got wrong.

#maple #ai #frontend #engineering #open-source