Alt+click any React or Svelte element in your localhost dev server to open its source file in Zed or Neovim at the exact line and column.
No build step. No bundler. Just a Manifest V3 browser extension and a tiny Node.js native messaging host.
If the player doesn't load inline on your client, download or open the demo here.
- Browser extension injects a script into pages on
localhost/127.0.0.1. When you Alt+hover an element it gets highlighted; when you Alt+click it walks the React fiber tree (_debugSource) or the Svelte__svelte_metachain to find the file, line, and column. - The extension hands that info to a Node native messaging host running locally on your machine.
- The host opens the file in your configured editor:
- Zed — via
zed path/to/file:line:col - Neovim — via
nvim --server <socket> --remote-sendinto a runningnvim --listeninstance
- Zed — via
Works on any localhost dev server (Vite, Next.js, CRA, SvelteKit, Remix, Astro, etc.) as long as source-location info is preserved by your bundler.
- macOS (the install script targets macOS native messaging directories — see Other platforms below).
- Node.js 18+.
- A Chromium-based browser: Chrome, Chromium, Brave, Edge, or Arc.
- Zed or Neovim installed.
- A dev server with debug source info enabled:
- React: needs
@babel/plugin-transform-react-jsx-source(default in Vite-React, CRA, and Next.js dev builds). - Svelte: must be compiled with
dev: true(the SvelteKit / Vite Svelte plugin default in dev).
- React: needs
git clone https://github.com/morethancoder/click-to-source.git
cd click-to-source- Open
chrome://extensions(orbrave://extensions,edge://extensions, etc.). - Toggle Developer mode on.
- Click Load unpacked and select the
extension/folder.
The extension's ID is pinned to kjlafkbahaheimiplchgfmefgeljmbhn on every machine via the key field in manifest.json, so the native host's allowlist is the same for everyone — no need to copy IDs.
node server/install.jsThis writes a small JSON manifest into the native-messaging-hosts directory of every supported browser it finds:
~/Library/Application Support/Google/Chrome/NativeMessagingHosts/~/Library/Application Support/Chromium/NativeMessagingHosts/~/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts/~/Library/Application Support/Microsoft Edge/NativeMessagingHosts/~/Library/Application Support/Arc/User Data/NativeMessagingHosts/
To uninstall:
node server/install.js --uninstallClick the extension's toolbar icon. The popup should say "Native host ready — editor: zed". If it says the host is not registered, re-run step 3 and reload the extension.
On first run the host writes a default config to ~/.click-to-source.json:
Some bundlers (Vite, SvelteKit) report file paths relative to the project root. The host has no way to know which project an http://localhost:5173 page belongs to, so you have to tell it:
{
"originRoots": {
"http://localhost:5173": "/Users/me/code/my-app",
"http://localhost:3000": "/Users/me/code/other-app"
}
}If a page reports an absolute path (CRA, Next.js dev), originRoots isn't needed.
If your dev server reports paths as they appear inside a container (e.g. /app/src/...) but the same files live somewhere else on your host:
{
"pathRewrites": {
"/app/src/": "/Users/me/code/my-app/src/"
}
}Set editor to "nvim" (or "neovim"), then start an nvim instance listening on the configured socket:
nvim --listen /tmp/nvim.sockSubsequent Alt+clicks will jump that nvim window to the file/line.
- Open your dev app at
http://localhost:<port>. - Hold Alt (Option on macOS) — your cursor will outline elements that have source info.
- Click. Your editor jumps to the file and line.
If nothing happens, open the page's DevTools console — both the extension and the native host log clear errors there. The native host also writes a log to /tmp/click-to-source-host.log:
tail -f /tmp/click-to-source-host.logThis is a localhost-only tool. The extension's content script and web_accessible_resources are scoped to localhost, 127.0.0.1, and *.localhost only. It does not run on any public site.
The native host:
- Runs on stdio (Chrome's native messaging protocol). It is started by Chrome on demand and exits after handling each message.
- Only accepts messages from extensions whose ID is in the
allowed_originsfield of the host manifest installed byserver/install.js. By default this is just the pinned IDkjlafkbahaheimiplchgfmefgeljmbhn. - Reads its config from
~/.click-to-source.json. It refuses to open files that don't exist on disk after path resolution. - Spawns the editor binary with a deliberately minimal
PATH(/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin).
If you fork the extension and regenerate its key, update PINNED_EXTENSION_ID in server/install.js and re-run install.
The install script currently writes manifests only into macOS-style paths. Linux and Windows users can still use the project — they just need to drop com.clicktosource.host.json (the same content install.js generates) into their browser's native messaging host directory by hand. PRs welcome.
| Symptom | Fix |
|---|---|
| Popup says "Native host not registered" | Run node server/install.js, then reload the extension. |
| Click does nothing, console says "No source info on this element" | Your bundler stripped debug info. For React, ensure @babel/plugin-transform-react-jsx-source runs (it does by default in dev). For Svelte, ensure dev: true. |
| Error: no projectRoot configured for origin "..." | Add an entry under originRoots in ~/.click-to-source.json. |
| Error: file does not exist on disk: ... | Your bundler reports a path that doesn't match disk — add a pathRewrites entry to remap it. |
| Error: nvim socket not found at /tmp/nvim.sock | Start nvim with nvim --listen /tmp/nvim.sock, or change nvimSocket in the config. |
zed / nvim not found |
The host spawns with a minimal PATH. Set zedBin / nvimBin to an absolute path in the config. |
There's no build step or test suite. Edit files in place:
- Extension changes: reload at
chrome://extensions. - Native host changes: no reinstall needed — Chrome respawns the host per message.
See CLAUDE.md for an architecture overview.
{ "editor": "zed", // "zed" or "nvim" "zedBin": "zed", // binary name or absolute path "nvimBin": "nvim", "nvimSocket": "/tmp/nvim.sock", // start nvim with: nvim --listen /tmp/nvim.sock "originRoots": {}, // { "http://localhost:5173": "/abs/path/to/project" } "pathRewrites": {} // { "/app/src/": "/Users/me/repo/src/" } }