uipin
v0.1.0
Published
Pin UI comments on your running React app and hand them to a coding agent, with the source file and line attached
Maintainers
Readme
uipin
Pin comments on your running React app, collect them, and hand the batch to a coding agent — with the source file and line already attached.
You are looking at a screen and something is off. Normally you describe it and the agent goes hunting through the codebase. uipin lets you point at it instead:
--- [1/2] "카드 간격이 들쭉날쭉해 보임" ---
선택: 영역 드래그 · 요소 3개 · 뷰포트 375×812
1) <article class="rounded-xl bg-w...">
AnniversaryCard @ src/components/Card.tsx:12:5
key "결혼기념일"
class rounded-xl bg-white shadow-sm p-4
padding: 16px · border-radius: 12px
327×84 · 부모 327×280
2) <article class="rounded-xl bg-w...">
AnniversaryCard @ src/components/Card.tsx:12:5
key "첫 만남"
class rounded-xl bg-white shadow-sm p-[14px]
padding: 14px · border-radius: 12px ← the odd one out
327×80 · 부모 327×280Three cards, same component, same line. The React key says which is which, and
p-[14px] next to p-4 makes the bug visible without anyone diagnosing it.
What it reports
- Where it lives — file, line and column of the JSX that rendered it, plus the components that own it
- What is actually on screen — the class list next to the computed values,
so a
p-3that renders 14px stands out - Viewport size — a layout that only breaks at 375px is unreproducible without it
- Text overflow — measured with a Range, because
scrollWidthreports nothing once an ancestor setsoverflow: hidden
It reports observations and no diagnosis. Deciding what is wrong is the agent's job, and it can read the design tokens out of your codebase itself.
Install
npm i -D uipin1. Mount the picker
// app/layout.tsx
import { UipinMount } from "./uipin-mount";
// ...
{process.env.NODE_ENV === "development" && <UipinMount />}// app/uipin-mount.tsx
"use client";
import { useEffect } from "react";
import { startUipin } from "uipin";
export function UipinMount() {
useEffect(() => {
startUipin();
}, []);
return null;
}2. Add the queue route
// app/api/uipin/route.ts
export { GET, PUT, DELETE } from "uipin/next";The browser mirrors its queue through this route to
node_modules/.cache/uipin/queue.json, where the agent reads it.
3. ⚠️ Exclude the route from auth middleware
If your app has middleware that guards /api, add an exemption. The picker
posts without a session, and middleware that answers with a login page returns
200 — so the write silently does nothing.
// middleware.ts
export const config = {
matcher: ["/((?!_next/static|api/uipin|...).*)"],
};uipin turns the sync dot red and logs when this happens, but it is the single most common setup mistake.
4. Register the MCP server
npm i -D uipin-mcp// .mcp.json
{
"mcpServers": {
"uipin": {
"command": "node",
"args": ["./node_modules/uipin-mcp/src/server.ts", "."]
}
}
}Requires Node 22.6+, which strips TypeScript types natively.
Use
| | |
|---|---|
| ⌥G | enter and leave pick mode |
| hover | highlight the element under the cursor |
| click | pin it |
| drag | pin several elements as one comment |
| ↑ ↓ | widen or narrow the selection — outward through ancestors, or between nesting levels after a drag |
| Esc | cancel |
The badge counts what is pinned. Click the count to review the list and delete individual items. The queue survives reloads and navigation, so a review can span several pages.
Then, in your agent:
쌓아둔 거 처리해줘
It calls get_queue, works through the batch, and calls clear_queue when
done.
Tools
| Tool | |
|---|---|
| get_queue | Read the pinned comments |
| clear_queue | Empty the queue once the batch is handled |
Requirements
- React 19 in a development build — the source locations come from
_debugStack, which production builds do not carry - Next.js — the queue route ships for Next today. The browser half is framework-agnostic; other adapters are not written yet.
- Chromium — Firefox and Safari lack the V8
CallSiteAPI, so source locations degrade to string-parsed stack traces - Server Components have no client fiber, so elements rendered purely on the server cannot be traced back
How it works
The hard part — turning a DOM node into a source location — is
react-grab's. React's dev build
attaches an Error to every jsxDEV() call, and react-grab reads that stack
and resolves it through the source map. uipin pins it at 0.1.50.
What uipin adds on top: path normalization, style condensing, overflow measurement, the queue, nesting levels, and the handoff to an agent.
react-grab is pre-1.0 and moves quickly, so uipin depends on an exact version rather than a range. Upgrading it is a deliberate release here.
Development
pnpm install
pnpm --filter uipin build
pnpm --filter uipin test
pnpm -C fixture dev # test app on :3100, with three planted bugsfixture/ is a small Next app whose bugs are documented in its README — the
expected values double as the accuracy oracle.
License
MIT
