Shutterbug

For the teams that run client software

Feedback from the people using the build, with the context attached.

Shutterbug sits in your dev, demo and preview environments. A client points at what is wrong and writes a line. You get the route, the build, the failing request, the console and a masked screenshot in your backlog, ready for a developer or their agent. A camera, not a chatbot. Nothing waits on a model.

The bar, open over a client's billing app. What is in the box is visible on the box.

What a report carries

Everything the developer would have asked for, attached before anyone asks.

  • 01screenshot, masked in the browser
  • 02the picked element, outlined
  • 03a line from the reporter, optional
  • 04one press
  • 05route
  • 06build sha, dirty or not
  • 07the failing request, with its body
  • 08console errors
  • 09browser and viewport

Install

Ten minutes, two files.

Three env values, a token route behind your sign-in, and one tag on signed-in pages. The console tells you when the first report lands. Free for two projects.

  1. 01 · Token route
    app/api/shutterbug/token/route.ts
    import { auth, currentUser } from "@clerk/nextjs/server";
    
    export async function GET() {
      const { userId } = await auth();
      if (userId === null) {
        return Response.json({ error: "sign_in_required" }, { status: 401 });
      }
      const user = await currentUser();
      const url = process.env.SHUTTERBUG_URL + "/api/reporter-tokens";
      const upstream = await fetch(url, {
        method: "POST",
        headers: {
          authorization: "Bearer " + process.env.SHUTTERBUG_PROJECT_SECRET,
          "content-type": "application/json",
        },
        body: JSON.stringify({
          projectKey: process.env.SHUTTERBUG_PROJECT_KEY,
          reporter: {
            id: userId,
            email: user?.primaryEmailAddress?.emailAddress ?? null,
          },
        }),
        cache: "no-store",
      });
      return new Response(upstream.body, {
        status: upstream.status,
        headers: {
          "content-type": "application/json",
          "cache-control": "no-store",
        },
      });
    }
  2. 02 · Tag
    app/(app)/layout.tsx
    import Script from "next/script";
    
    <Script
      src="https://shutterbug.dev/shutterbug/loader.js"
      strategy="afterInteractive"
      data-shutterbug-project-key="pk_…"
      data-shutterbug-endpoint="https://shutterbug.dev"
      data-shutterbug-token-url="/api/shutterbug/token"
      data-shutterbug-release={process.env.VERCEL_GIT_COMMIT_SHA}
    />

Any stack: the route is one POST.Read the docs

One press replaces the call, the screenshot and the paragraph explaining it.

  1. 01 · Capture

    Your client presses the mark inside the build, points at the thing, writes a line. The widget attaches the route, the build, the failing request, the console and a masked screenshot before anyone asks.

  2. 02 · Backlog

    Every report lands on one board as a receipt: what was said, what was shown, what the browser was doing. It is to do or done. The client sees it move.

  3. 03 · Tackle

    Developers, or their agents, pull the report with the whole picture attached and update the board when the change ships. No transcribing a call, no chasing a screenshot in Slack.