DEV Community

Cover image for Introducing Maple: The Frontend Review Toolkit
Nitzan Kletter
Nitzan Kletter

Posted on Originally published at blog.nitzan.fyi AI-assisted

Introducing Maple: The Frontend Review Toolkit

Coding agents made writing code cheap. Reviewing what they build did not get any faster.

You work on a new, big feature. You want feedback. You share a preview URL with your team and, best case, nobody has notes. (A myth, in my experience.)

Usually the notes arrive over the day, across three threads and a DM ("the spacing under the chart looks off", a screenshot, no other context) while you're deep in three other things. Then you spend the evening working out which note belongs to which task and translating each one into something your agent can act on. You come back, and it changed the wrong button.

The fix was never the expensive part. The translation was.

So I built Maple: comments are written directly on the app, UI edge cases are simulated in plain language, your agent gets all the context it needs to fix them, and CI enforces it. No middleman. A reviewer can point at a component, drag a box over part of the page, or select the words that are wrong.

What it is

Maple is an open-source frontend review toolkit: one route in your application, one script in the preview build. A reviewer points at what is wrong and says why. 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 also puts the page into whatever state someone wants to review, and checks the rules a machine can check without anyone looking.

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

  • @maple-kit/core: the server SDK, the overlay controller and the connector contracts.
  • @maple-kit/ui and @maple-kit/react: the marks, the island and the composer, and the hooks underneath them.
  • @maple-kit/mcp and @maple-kit/cli: the MCP server an agent talks to, and the maple command.
  • @maple-kit/mock: rewrites a page's API responses into a named state.
  • @maple-kit/lint: design-system rules, read off the rendered page.
  • @maple-kit/classifier: scores a comment as it is written.

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 review is a team sport, 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

A build-time tagger marks every JSX element with the file, line and column it was written at, so a comment arrives pointing at the source, not at a CSS selector that breaks on the next deploy. Alongside it: the viewport width at pick time, the theme, which disclosure elements were open, who wrote it and how strongly that identity is attested, and a screenshot taken automatically when the pick commits.

Once the comments are written, the agent kicks in over MCP:

Tool What it is for
list_comments Everything on a branch, newest first.
wait_for_comments Block until something new arrives.
get_comment_context Everything needed to act on one comment, including the mock it was written under, with a link that reopens the page in that state.
resolve_comment Mark one addressed, naming the commit, and tell the gate, so it clears without another push.

There is also start_solo, which pairs a deployed preview with a bridge on your machine so you can review a preview alone. Wait, read, fix, resolve. Nobody translated anything.

Any state, as anyone

The states that most need a visual review are the hardest to reach on a preview: empty, error, forbidden, loading, one item, a thousand items. Nobody seeds a staging account with zero projects just to see whether the empty state is embarrassing. So nobody sees it until a customer does.

With Maple Mock, the reviewer types the state they want in natural language ("mock this page with an empty state") and the page refreshes with the matching API calls mocked.

It goes past data, too. Say "as a member, with the merge forecast on" and Maple reads the sentence into a role, permissions and feature flags. OpenFeature and LaunchDarkly flags are answered on the wire. The server still acts as the real reviewer, so a mutation sent under a mock really happens; the mock changes what the page is shown, not who you are.

A comment written under a mock remembers the mock. So when the agent picks it up, it can reopen the exact page the reviewer was complaining about (the empty one, as a billing manager, with the flag off) instead of staring at the happy path and saying "looks fine to me".

The gate is the wedge

Maple posts a maple/visual-review check and blocks the merge while a comment is unaddressed, and, if you want it to, until somebody actually approves the preview.

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") });
Enter fullscreen mode Exit fullscreen mode

The rules nobody should have to point at

Some review comments shouldn't need a person at all. "That grey isn't one of our tokens." "This button is 20px tall." "This animates width." A machine can check those on every pull request, so @maple-kit/lint does.

It runs Chromium against the deployed preview and reads what the cascade, the theme and the media queries finally produced: off-token colours, sizes off the type scale, touch targets under 24px, text under WCAG AA contrast, motion on anything but opacity and transform, and motion that ignores prefers-reduced-motion. That last one is checked the only way you can from the outside: each viewport is read twice, once normally and once with reduced motion emulated, and whatever still moves in the second pass hard-coded its motion.

Maple stores nothing

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) {
      /* ... */
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

There are six kinds: store, media, identity, observability, gate and classifier. A connector's capabilities are exactly the methods it defines. The default store is the pull request itself (one Maple comment per PR, reposted rather than edited), so a fresh install needs nothing you do not already have.

And if you want a second opinion while you type, plug in a classifier and the comment is scored as you write it, against five pillars: specific, actionable, concise, standalone and placed.

Where it is

At 0.18, built in the open, and already usable. 0.x makes no compatibility promise: when breaking an interface is the right shape I break it, and the changeset says what broke. The MCP server is listed in the official MCP Registry as io.github.maple-kit/maple.

What is not built yet: a hosted shared store (a file store in a preview pod loses its comments when the pod goes, so use GitHub or write a connector for now), a session replay link on every comment, accepting the agent's fix in the preview, and the whole overlay as terminal commands. Next up is letting the agent post its fix on the comment, with a before and after, so the reviewer accepts it right where they wrote it and the gate clears without a push.

To try it on an app you already run, with no accounts and nothing wired in: npx @maple-kit/cli review --port 5173, with your dev server's port. Comments are kept as files under .maple/.

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.

Top comments (1)

Collapse
 
beladevo profile image
beladevo •

One of the best open-source projects for truly bridging the gap between developers and PMs. I’ve already used it and really love it!