English | 简体中文
Pin feedback to UI. Trace it to source.
SourcePin is a local-first visual feedback layer for coding agents. Click an element in a running Vite app, leave a comment, and SourcePin records the route, DOM identity, component, and source location in the repository. A later Codex session can pick up the pin, edit the right code, and return the result for human review.
click UI → leave feedback → agent fixes source → human accepts or reopens
No cloud account is required. Pins live under .sourcepin/, travel with the project when you choose to commit them, and remain human-owned at the final acceptance step.
The real SourcePin development overlay running on the included billing demo: numbered targets stay attached to the page while the complete drawer tracks feedback and human review.
Run the same interactive demo locally:
pnpm install
pnpm demoThen open http://127.0.0.1:4190, click SourcePin, and choose a mode: Element clicks to create a durable pin or drags a source-addressed target; Text keeps native browser selection and exposes Copy context / Drag to agent; Region draws a viewport rectangle and exports up to eight intersecting DOM/source candidates. All drag paths use bounded text/plain plus structured application/x-sourcepin, and never submit destination input automatically. Region coordinates are evidence only, not persistent identity. Terminal support varies, so normal pin persistence and copy remain the reliable fallback.
- Next.js App Router support through the verified
next dev --webpackpath, with a project-local Route Handler and production stripping. - Direct element drag into compatible coding-agent/terminal text targets.
- Native text selection with Copy context and Drag to agent.
- Region selection with bounded intersecting DOM/source candidates.
- Compact, mutually exclusive Element / Text / Region controls that do not auto-submit agent input.
The durable pin/MCP workflow remains canonical for cross-session work; drag and copy modes are fast handoff paths for an agent session that is already open.
A screenshot or issue saying “fix this button” leaves an agent guessing. SourcePin gives it structured context:
- the route and internal UI surface where the problem appeared;
- a stable selector and semantic fallback anchor;
- the React/Vue component and project-relative source location;
- the original comment, viewport, and element geometry;
- a constrained review state that the agent cannot self-accept.
SourcePin is useful for interaction, copy, layout, and state bugs. Screenshots are optional evidence—not a requirement and not an automatic visual-model call.
pnpm add -D sourcepinPlace SourcePin before the framework plugin so it can inspect the original JSX/TSX or Vue template.
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import sourcepin from "sourcepin/vite";
export default defineConfig({
plugins: [sourcepin(), react()],
});The same ordering applies to Vue:
plugins: [sourcepin(), vue()]// src/main.tsx, src/main.ts, or equivalent
import { mountSourcePin } from "sourcepin";
if (import.meta.env.DEV) {
void mountSourcePin();
}Start the app, click the purple SourcePin button, select an element, and write feedback. The left-side Feedback button opens the review drawer.
Pins are saved as validated JSON:
.sourcepin/pins/pin_<uuid>.json
Available in
sourcepin@0.2.0and later.
SourcePin supports Next.js App Router projects through the webpack development path. Configure Next to add source metadata and rewrite the browser protocol to a project-local Route Handler:
// next.config.mjs
import { withSourcePin } from "sourcepin/next";
export default withSourcePin({}, { root: process.cwd() });Use webpack while developing:
{
"scripts": {
"dev": "next dev --webpack"
}
}Add one catch-all App Router handler. The public /__sourcepin/* protocol is rewritten here because underscore-prefixed App Router folders are private:
// app/sourcepin-api/[...sourcepin]/route.ts
import { createSourcePinRouteHandlers } from "sourcepin/next";
export const dynamic = "force-dynamic";
const handlers = createSourcePinRouteHandlers({ root: process.cwd() });
export const { GET, POST, PATCH } = handlers;Mount the client overlay only when the server layout is running in development:
// app/sourcepin.tsx
"use client";
import { useEffect } from "react";
import { mountSourcePin } from "sourcepin";
export function SourcePinOverlay() {
useEffect(() => {
let dispose: undefined | (() => void);
void mountSourcePin().then((value) => { dispose = value; });
return () => dispose?.();
}, []);
return null;
}// app/layout.tsx (inside <body>)
{process.env.NODE_ENV === "development" ? <SourcePinOverlay /> : null}The adapter currently targets next dev --webpack; Turbopack is not claimed. The package gate installs the packed tarball into an independent Next.js 16 App Router consumer, creates and reloads a real pin in Chromium, reads it back through the Route Handler and repository store, runs next build --webpack, then verifies that production HTML contains neither source metadata nor the overlay.
The overlay provides:
- element hover and selection;
- numbered markers attached to live targets;
- a collapsible feedback drawer;
Open,Review, andAcceptedfilters;- cross-route navigation and internal-tab restoration;
- human-only Accept and Reopen actions.
Install the bundled SourcePin skill into the consuming project:
pnpm exec sourcepin initA CLI-only agent can then discover and inspect work:
pnpm exec sourcepin next --json
pnpm exec sourcepin context pin_<id>After changing the code and running the relevant tests, the agent returns the pin for review:
pnpm exec sourcepin changed pin_<id> src/components/Card.tsxThe status lifecycle is deliberately asymmetric:
open ──agent──▶ changed-awaiting-review ──human──▶ accepted
▲ │ │
└────────────── human reopen ◀──────────────────────────┘
The agent can report a verified change. Only a human can accept it.
SourcePin includes a stdio MCP server. Register it from the consuming repository:
codex mcp add sourcepin -- pnpm exec sourcepin-mcpVerify the registration:
codex mcp listAvailable tools:
| Tool | Purpose | Mutates pin state |
|---|---|---|
list_pins |
List and filter feedback | No |
get_context |
Return the pin, bounded source snippet, and surface metadata | No |
mark_changed |
Move an open pin to human review after verified edits | Yes |
There is intentionally no MCP accept tool. Read-only tools declare MCP safety annotations, and all operations are constrained to the current repository.
SourcePin never persists an HTMLElement. DOM nodes may disappear when a React/Vue branch unmounts, a modal closes, or a virtualized row is recycled. Instead, each pin stores several independent signals.
Selector preference:
data-testid
→ data-sourcepin-key
→ id
→ injected source metadata
→ bounded DOM path
If the preferred selector drifts, SourcePin scores semantic candidates using the element tag, text, role, accessible name, and source location. It restores a marker only when one candidate wins unambiguously.
Target states shown by the drawer:
| State | Meaning |
|---|---|
attached |
One visible target is currently resolved |
dormant |
The target is hidden/unmounted but its UI surface can be restored |
unresolved |
No reliable current target exists; the feedback remains available to the agent |
ambiguous |
Multiple candidates are equally plausible, so SourcePin refuses to guess |
SourcePin never falls back to drawing a marker at stale screen coordinates.
Standard ARIA tabs are detected through role="tabpanel" and the corresponding role="tab". Clicking a dormant feedback card can reactivate the recorded tab and resolve the target again.
For custom internal views, add an explicit surface:
<section
data-sourcepin-surface="settings.billing"
data-sourcepin-surface-label="Billing"
>For repeated rows with otherwise identical text and source metadata, add a stable business key:
<article data-sourcepin-key="user:42">Stored activation recipes are limited to structured actions such as a selector-based click. Pin files cannot contain arbitrary JavaScript.
| Surface | Status |
|---|---|
| Vite middleware and overlay | Supported |
| React JSX/TSX source injection | Supported |
| Vue SFC template source injection | Supported |
| React/Vue conditional unmount and hidden DOM | Supported |
| ARIA tabs and explicit custom surfaces | Supported |
Next.js App Router (next dev --webpack) |
Supported since 0.2.0; independently dogfooded |
| Nuxt-native and SvelteKit adapters | Out of scope |
| Browser extension | Out of scope; project integration provides more reliable source mapping |
Vue injection uses the Vue template AST and leaves <script> strings and component tags untouched. JSX/TSX injection uses the Babel AST. Both transforms fail open rather than blocking the development build on unsupported syntax.
SourcePin is designed for local development:
- Vite middleware writes only validated files under
.sourcepin/pins/; - each repository root is an independent store, so parallel Codex sessions in different projects cannot see one another's pins;
- updates to the same pin use a project-local atomic lock and atomic rename, so concurrent agents cannot both complete the same
open → changed-awaiting-reviewtransition; - pin IDs, routes, selectors, geometry, source paths, surfaces, and evidence paths are validated;
- request bodies are bounded;
- source-context reads reject lexical and symlink escapes from the repository;
- comments are treated as untrusted feedback, not executable agent instructions;
- UI text is rendered without unsafe HTML insertion;
- final acceptance remains human-owned.
If middleware is unavailable, the browser can temporarily fall back to localStorage. The fallback key includes a stable fingerprint of the Vite project root, preventing projects that reuse the same localhost origin from sharing fallback pins. Repository-backed pins remain the canonical path for cross-session agent work.
A pin may reference screenshots produced by a separate adapter or sidecar:
{
"evidence": {
"screenshots": [".sourcepin/assets/pin_example.png"]
}
}Evidence is limited to 20 project-local PNG/JPEG/WebP files under .sourcepin/assets/, which is ignored by default. SourcePin does not capture the screen or invoke a visual model automatically.
sourcepin init
sourcepin list [--json]
sourcepin next [--json]
sourcepin show <pin_id>
sourcepin context <pin_id>
sourcepin changed <pin_id> <changed_files...>
sourcepin accept <pin_id>
sourcepin reopen <pin_id>
Use changed from an agent session. Reserve accept and reopen for human review.
Requirements: Node.js 20+ and pnpm 10.15.1.
pnpm install
pnpm exec playwright install chromium
pnpm gatepnpm gate runs:
- strict TypeScript checking;
- unit and protocol tests;
- the library build;
- the billing demo build;
- a packed-tarball consumer smoke;
- a real stdio MCP handshake and tool invocation;
- Chromium end-to-end tests for Element/Text/Region handoff modes, persistence, scrolling, routes, tabs, hidden/transient DOM, selector drift, and browser review actions.
GitHub Actions runs the same gate on Linux. npm releases use Trusted Publishing/OIDC with provenance.
SourcePin is an early public release focused on proving the local UI-to-source feedback loop. The core Vite, React/Vue, Next.js App Router over webpack, CLI, MCP, review, packaging, and CI paths are implemented and tested. Turbopack support is not claimed. Nuxt-native, SvelteKit, and browser-extension adapters are out of scope. Comment editing/deletion and stronger source fingerprints remain possible future work.
