npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pinsource

v0.6.1

Published

Pin any UI element in your running web app to its source file. A floating in-browser devtool that resolves the React/Vite/Next component, file path, and route — then copies a structured reference block.

Downloads

937

Readme

pinsource

Click any element on your running app → get its source file. A floating devtool for React, Next.js, Vite, and any modern web stack.

Built for the moment you're staring at your app and thinking "where is this code?" Pick the element, copy a ready-to-paste reference, keep moving.

<ProductCard />
→ components/ProductCard.tsx:31

Quick start

Next.js (App Router)

npm install --save-dev pinsource

Add two small files. That's the entire setup.

1. Create the API route. The extra re-exports (runtime, dynamic) pin the handler to Node.js and disable caching — both required for the resolver to work:

// app/api/__pinsource/route.ts
export { POST, GET, runtime, dynamic } from "pinsource/next-route";

2. Mount the devtools loader in your root layout. PinsourceLoader renders nothing in production builds, so it's safe to leave in the tree:

// app/layout.tsx
import PinsourceLoader from "pinsource/loader";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        <PinsourceLoader />
      </body>
    </html>
  );
}

3. Run next dev and open your app. Look for the floating button in the bottom-right corner.

Verify the API route is mounted. Open http://localhost:3000/api/__pinsource in a browser — you should see:

{ "ok": true, "service": "pinsource", "runtime": "nodejs" }

If you get a 404, the route file is in the wrong place. If you get a 500, check your server logs — the most common cause is the route accidentally running on the Edge runtime (make sure you re-exported runtime as shown above).

Vite

npm install --save-dev pinsource
// vite.config.ts
import { defineConfig } from "vite";
import pinsource from "pinsource/vite-plugin";

export default defineConfig({
  plugins: [pinsource()],
});
// main.tsx (or wherever your root renders)
import PinsourceLoader from "pinsource/loader";

<>
  <App />
  <PinsourceLoader />
</>

Next.js (Pages Router)

// pages/api/__pinsource.ts
export { default } from "pinsource/next-route";

export const config = { api: { bodyParser: true } };

Then mount <PinsourceLoader /> in _app.tsx:

// pages/_app.tsx
import PinsourceLoader from "pinsource/loader";

export default function App({ Component, pageProps }) {
  return (
    <>
      <Component {...pageProps} />
      <PinsourceLoader />
    </>
  );
}

CRA, Webpack, Remix, anything else

Mount the loader in your root component, then run the standalone resolver:

npx pinsource-server

Or bake it into your dev script:

{
  "scripts": {
    "dev": "trap 'kill 0' INT TERM; your-dev-command & pinsource-server & wait"
  }
}

Using it

  1. Click the floating button (or press ⌘⇧C / Ctrl⇧C)
  2. Hover to highlight, click to select
  3. Hit Copy source — paste it anywhere

Output shapes

Copy source — LLM-optimized fenced block. Every field is key: value on its own line, file refs use the path:line format that editors and CLIs open natively. Drop it into Claude, Cursor, ChatGPT, or a PR comment and any tool can parse it:

```pinsource
component: ChatInput
tag: textarea
route: /chat
source: app/chat/components/ChatInput.tsx:14
page: app/chat/page.tsx
chain: ChatInput > ChatComposer > ChatSession
dom: main > div.composer > textarea.input
```

Full prompt — structured block with context, for AI assistants:

**Component:** `ChatInput`
**Route:** `/chat`
**DOM tag:** `<div>`

**File references (open these first):**
- @app/chat/components/ChatInput.tsx:14  ← component definition
- @app/chat/page.tsx  ← page where it was picked

**Component chain (nearest → outermost):**
**ChatInput** → ChatComposer → ChatSession

**DOM path:**
`div#root > main > div.flex > div.composer`

**Computed styles:**
- size: 720 × 56
- display: flex
- direction: row
- gap: 8px
- padding: 12px
- background: rgb(17, 17, 20)

Screenshot — the camera button captures the picked element as a PNG and copies it to your clipboard. Paste straight into Claude, Slack, or a PR.

Keyboard shortcuts

| Shortcut | Action | | ---------------------------- | ------------------------- | | ⌘ Shift C / Ctrl Shift C | Toggle the element picker | | Esc | Cancel picking |


Configuration

<PinsourceLoader
  defaultCorner="bottom-right"
  shouldRender={() => true}
  skipComponents={["FeatureFlagGate"]}
/>

| Option | Type | Default | Description | | ---------------- | -------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------ | | defaultCorner | "top-left" \| "top-right" \| "bottom-left" \| "bottom-right" | "bottom-right" | Initial panel position. Panel is draggable after mount. | | shouldRender | () => boolean | process.env.NODE_ENV !== "production" | Gate visibility. Use this to expose the panel in staging or behind a flag. | | skipComponents | string[] | [] | Extra component names to skip when walking the fiber ancestor chain. | | serverUrl | string | auto-detected | Override the resolver URL. Usually unneeded — the panel finds the endpoint automatically. |

Resolver environment variables

Only relevant for the standalone server — the Next.js route and Vite plugin inherit the project's process.cwd().

| Variable | Default | Description | | ---------------- | ------------------------------------------- | ------------------------------------------------- | | PINSOURCE_PORT | 9101 | Port the standalone HTTP server binds to. | | PINSOURCE_CWD | process.cwd() | Directory the server greps within. | | PINSOURCE_DIRS | "app components handlers lib src pages" | Space-separated list of subdirectories to search. |


How it works

  1. Fiber walk. When you click an element, pinsource reads the React fiber attached to the DOM node and walks upward, collecting the displayName or name of each real component. Framework wrappers (router internals, error boundaries, providers) are skipped automatically.
  2. Source resolution. If the bundler injected _debugSource (Next.js dev, Vite dev, CRA dev all do this by default), the exact file:line is read straight from the fiber — no network call. Otherwise, the panel hits a local /resolve endpoint that runs a scored grep over your source directories and picks the highest-confidence definition, skipping re-exports and imports.
  3. Rendering. The panel shows the resolved file, ancestor chain, and key computed styles, with one-click copy and screenshot actions.

Endpoint discovery

On the first pick, the panel probes these endpoints in parallel and caches the winner:

  1. /__pinsource/resolve (Vite plugin)
  2. /api/__pinsource (Next.js route)
  3. http://localhost:9101/resolve (standalone server)

You never have to set a URL unless you want to override it.


Troubleshooting

Next.js: "source file: not found"

The panel picks elements but the Source file card shows not found.

  1. Verify the route is mounted. Open http://localhost:3000/api/__pinsource — you should get a { ok: true } JSON response. If 404, the file is in the wrong place (should be app/api/__pinsource/route.ts for App Router, pages/api/__pinsource.ts for Pages Router).
  2. Verify the runtime. Open your browser DevTools → Network tab → pick an element. Find the __pinsource request. If the response is 500 and the server log says "child_process" is not supported in the Edge Runtime, your route.ts is missing export { runtime } from "pinsource/next-route" — add it.
  3. Check NODE_ENV. The route returns 403 in production. next dev runs in development by default; make sure you haven't set NODE_ENV=production in .env.local.
  4. Fall back to the standalone server. Run npx pinsource-server in a second terminal — it binds to localhost:9101 and works regardless of framework. The client auto-detects it.

Vite: devtools show but "not found"

Make sure the plugin is registered in vite.config.ts and that you're running vite dev (not preview or build). The plugin is apply: "serve" only.

TypeScript error: "Cannot find module 'pinsource/next-route'"

Update to [email protected]+ — earlier versions shipped the subpath export without type declarations. If already on latest, make sure your tsconfig.json has "moduleResolution": "bundler" or "node16" so subpath exports are honored.

Checking the resolver directly

# Health check
curl http://localhost:3000/api/__pinsource

# Try to resolve a component
curl -X POST http://localhost:3000/api/__pinsource \
  -H 'Content-Type: application/json' \
  -d '{"kind":"component","name":"ProductCard"}'

# Try to resolve a route
curl -X POST http://localhost:3000/api/__pinsource \
  -H 'Content-Type: application/json' \
  -d '{"kind":"page","route":"/dashboard"}'

Each should return {"file":"...","line":N} or an empty {} if not found.


Security

  • Dev-only: the loader renders nothing when NODE_ENV === "production", and the Next route returns 403 in production.
  • Localhost-only: the standalone server binds to 127.0.0.1.
  • No network calls: nothing about your code ever leaves the machine — all resolution is local grep + find.

Exports

import Pinsource from "pinsource";                            // main component
import PinsourceLoader from "pinsource/loader";               // lazy, dev-only wrapper
import { useElementPicker, resolveComponentFile, resolvePageFile }
  from "pinsource";                                           // primitives
import type { DevToolsOptions, PickedElement, PickerState }
  from "pinsource";

Requirements

  • React 18+
  • Node 18+

License

MIT