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

@t4stack/solid-grab

v0.1.1

Published

Copy any UI element of a Solid.js 2 app for your agent: HTML + component stack + file:line

Downloads

45

Readme

solid-grab

Copy any UI element of a Solid.js 2 app for your agent.

Hold ⌘ (Ctrl on Windows/Linux), hover an element, press ⌘C, and you get agent-ready context on the clipboard: the element's HTML, its component stack, and file:line:col source locations.

[<li class="todo" data-index="0">Buy milk</li> in TodoItem (at src/components/TodoItem.tsx:2:10)
  in TodoList (at src/components/TodoList.tsx:4:8)
  in App (at src/App.tsx:4:16)]

Requirements

  • solid-js ≥ 2.0.0-rc.8
  • @solidjs/vite-plugin ≥ 3.0.0-next.43
  • Vite ≥ 6

Dev-only. Nothing from solid-grab ships in production builds.

Install

pnpm add -D @t4stack/solid-grab
// vite.config.ts
import { defineConfig } from "vite";
import solid from "@solidjs/vite-plugin";
import solidGrab from "@t4stack/solid-grab/vite";

export default defineConfig({
  plugins: [solid({ start: true }), solidGrab()],
});

Plugin array order does not matter. That's it — under vite dev the plugin stamps a data-sg-loc="file:line:col" attribute on native JSX elements and auto-loads the runtime; under vite build it does nothing.

Usage

  • Hold ⌘ (Ctrl on Windows/Linux) and hover an element to highlight it.
  • ⌘C while hovering copies its context to the clipboard.
  • Click to select an element (shift-click to add/remove from the selection); drag to marquee-select.
  • Right-click, or ⌘. on a selection, opens the context menu: Copy, Open in editor, plus any plugin-registered actions.
  • With a selection: arrow keys walk the tree (↑ parent, ↓ first child, ←/→ siblings), Enter copies, Esc backs out one step at a time (menu → selection → deactivate).
  • A small draggable pill toolbar toggles solid-grab on/off and can be collapsed.

In activationMode: "toggle" the activation key behaves the same once grab mode is on: press it with something hovered or selected and it copies. It only turns grab mode back off when there is nothing to copy. Esc always exits.

How it works

Solid 2 attaches nothing to DOM nodes and the compiler emits no per-element source location, so solid-grab combines two pieces:

  1. Build-time attribute stamping — a Vite pre-transform (enforce-independent, order: "pre", so it runs before the Solid compiler) inserts data-sg-loc="file:line:col" onto native JSX elements.
  2. A runtime bridge@t4stack/solid-grab/runtime installs DEV.hooks.onOwner, tracks binding effects' _devElement back to the owner tree, and walks owners up to component frames (_component.name, and each component's location from the HMR refresh proxy).

Both only run in dev. See docs/superpowers/specs/2026-09-16-solid-grab-design.md for the full design.

Browser extension

The extension injects solid-grab into any page:

  • With the Vite plugin present (the page announces solid-grab:init / window.__SOLID_GRAB__), the extension defers to it — full HTML + component stack + source locations.
  • Without it, the extension injects its own bundled copy in HTML-and-selector-only mode (no component names or source). On non-localhost hosts, copied content is converted to Markdown instead of raw HTML.

Build and load unpacked:

pnpm --filter @solid-grab/extension build
  • Chrome: chrome://extensions → enable Developer mode → Load unpacked → select packages/extension/.output/chrome-mv3.
  • Firefox: about:debugging#/runtime/this-firefox → Load Temporary Add-on → select packages/extension/.output/firefox-mv3/manifest.json.

Firefox MV3 and site access. Firefox treats <all_urls> host permissions as optional, so a normally installed (signed) build starts with no access to any page and the content script never runs. Grant it once in about:addons → Solid Grab → Permissions → allow access to all sites (or to the specific sites you want). Temporary add-ons loaded via about:debugging are exempt and work straight away.

  • Safari: pnpm --filter @solid-grab/extension safari, then open the generated Xcode project in packages/extension/.output/safari-xcode and run it once to install the extension.

Plugin options (@t4stack/solid-grab/vite)

| Option | Type | Default | Description | |---|---|---|---| | enabled | boolean | true under vite (serve), false under vite build | Turn the transform on/off. | | include | RegExp | /\.[jt]sx$/ | Files eligible for stamping. | | exclude | RegExp | /node_modules/ | Files excluded from stamping. | | attribute | string | "data-sg-loc" | Attribute name used for the stamped location. |

Runtime options (Options, passed to init() / window.__SOLID_GRAB__.setOptions())

| Option | Type | Default | Description | |---|---|---|---| | enabled | boolean | true | Whether solid-grab is enabled at all. | | activationKey | string \| ActivationKey | Meta+C (mac) / Ctrl+C (elsewhere) | The hold/toggle key. | | activationMode | "hold" \| "toggle" | "hold" | Hold the key vs. toggle it on/off. | | keyHoldDuration | number | 100 | Milliseconds the key must be held before activating. | | allowActivationInsideInput | boolean | false | Allow activation while focus is in an input/textarea/contenteditable. | | maxContextLines | number | 3 | Max component-stack lines in the clipboard output. | | getContent | (elements: Element[]) => string \| Promise<string> | — | Override how selected elements are serialized (used by the extension off-localhost). | | bridge | SolidGrabDevBridge \| null | window.__SOLID_GRAB_DEV__ | The runtime bridge; null forces HTML-only mode. |

API (window.__SOLID_GRAB__)

  • activate() / deactivate() / toggle() / isActive()
  • isEnabled() / setEnabled(enabled)
  • getToolbarState() / setToolbarState(state) / onToolbarStateChange(cb)
  • copyElement(elements) — copy one or more elements to the clipboard
  • getSource(element) — resolve SourceInfo for an element
  • getContext(element) — the clipboard text for one element
  • setOptions(options)
  • registerPlugin(plugin) / unregisterPlugin(name) / getPlugins()
  • dispose()

Events: solid-grab:init (fires once on boot, detail is the API), solid-grab:element-selected (fires on every copy, detail.elements is a summary of the copied elements).

Plugins

window.__SOLID_GRAB__.registerPlugin({
  name: "my-plugin",
  actions: [{ id: "log", label: "Log to console", onAction: (ctx) => console.log(ctx.context) }],
  hooks: { onCopySuccess: (elements, content) => {/* ... */} },
  options: { maxContextLines: 5 },
});

Plugins can add context-menu actions and hook into the copy lifecycle (onBeforeCopy, transformCopyContent, onCopySuccess, onCopyError). This is the seam a future agent-relay phase would use.

A plugin's options are applied once, when it is registered. unregisterPlugin(name) removes its actions and hooks but does not revert those options — solid-grab does not track which value an option had before, and another plugin or the host may have changed it since. Call setOptions() yourself if you need the previous value back.

Limitations / roadmap

  • Solid 2 only — the runtime depends on Solid 2 dev internals (_component, _devElement, DEV.hooks.onOwner); no Solid 1.x support.
  • Vite only — the transform is bundler-agnostic in principle, but only Vite glue exists today.
  • No agent relay yet — v1 is "core grab" (clipboard only); a relay/provider phase is out of scope for now.
  • No freezing of reactive updates while the overlay is active.

Development

pnpm install
pnpm build
pnpm test
pnpm e2e
pnpm --filter playground dev