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

@autono/pinbox-toolbar

v0.21.0

Published

The embeddable feedback toolbar: a vanilla web component (Shadow DOM isolation, zero runtime deps).

Readme

@autono/pinbox-toolbar

The embeddable feedback toolbar: a vanilla web component (Shadow DOM isolation, zero runtime deps).

Two build artifacts from one src/, both emitted by tsdown — there is no Vite build here. tsdown.config.ts exports an array of two configs, not one config with two formats: with fixedExtension: false an iife output would also be named .js and collide with the ESM entries, and a multi-entry iife build cannot share chunks.

  • dist/toolbar.iife.js — script-tag embed for any stack; served from a versioned CDN path (never latest-only). The entry point is Pinbox.init({ endpoint }), same as every other install path.
  • ESM build for bundlers — one entry per exports subpath.
<script src="https://cdn.example.com/@autono/[email protected]/toolbar.iife.js"></script>
<script>
  Pinbox.init({ endpoint: "http://127.0.0.1:4319" });
</script>

The iife entry is src/iife.ts, not src/index.ts, and that is load-bearing. An iife bundle assigns the entry module's namespace to globalName, so an entry exporting a Pinbox object yields Pinbox.Pinbox.init(…) on the host page. src/iife.ts re-exports the intended surface flat and exists for this bundle alone — it is absent from the ESM entry list and from exports, whose shapes are separate published contracts. src/build.test.ts evaluates the built bundle in a happy-dom realm and asserts both that Pinbox.init is callable and that Pinbox.Pinbox does not exist, because after the first publish this is a breaking change to a public API.

Chunking is part of the published contract: because the wrappers share src/index.ts, rolldown hoists the custom-element registration into a shared chunk and dist/index.js becomes a re-export shim. sideEffects must therefore stay the glob ./dist/*.js — naming only the shim would let bundlers tree-shake the dev plugins' bare import "@autono/pinbox-toolbar" away, silently. src/build.test.ts bundles that bare import with Vite and fails if the shape changes.

The ./vite and ./next subpath exports are dev-server plugins for the consumer's bundler. Vite is therefore an optional peerDependency with a deliberately wide range — it is their Vite, not ours, and pinning it would break installs. Pinbox itself has no Vite dependency.

Subpath exports (split into a separate package only if one ever needs independent versioning):

  • ./react, ./vue, ./svelte — thin wrappers; frameworks are optional peerDependencies and stay external, so nothing framework-shaped is bundled into dist/index.js.
  • ./vite, ./next — dev-server plugins that auto-inject the toolbar in dev and ensure the hub is running. Dev-only; excluded from prod builds.

./next is a separate plugin, not a re-export of ./vite, and Vite's own docs are the reason: transformIndexHtml "won't be called if you are using a framework that has custom handling of entry files (for example SvelteKit)." Next has no equivalent public hook for injecting a script tag into every dev page from next.config, so withPinbox() does only the half it can do honestly — keeping the hub daemon alive during next dev. The client-side mount is spelled out as a TODO in src/plugins/next.ts rather than faked.

Both plugin files are guest-rule code (AGENTS.md): they are evaluated inside the consumer's toolchain, and both the vite and next bins carry a #!/usr/bin/env node shebang, so they land on Node even when launched with Bun. They use the Node/Bun shared subset (node:*) deliberately — do not "fix" them.

Realtime, offline, attachments

  • Realtime — src/transport.ts speaks the frozen WS protocol (hello → catch-up → events) against GET /ws, authenticating at upgrade only via the pinbox.token.<token> subprotocol, because a browser cannot set headers on an upgrade. It reconnects with jittered 1s→30s backoff and replays from its persisted cursor, so a dropped socket costs no events. Either side of an excluding protocol window closes 4400 with a clear upgrade message; a bad token closes 4401.
  • Offline mirror — src/transport/mirror.ts keeps the cursor, the last-known pin list, and an outbox of pins drawn while offline in localStorage, namespaced per endpoint. Reconnect reconciles on one rule: the hub wins on status, the client wins on new pins. Storage writes never throw upward — private mode or a full quota degrades the mirror, never the toolbar.
  • Attachments — two capture modes, both webp-encoded in the browser and POSTed to /attachments; the pin then carries the returned path — never bytes. The default is a DOM snapshot of the pinned element (src/screenshot-dom.ts: clone, inline computed styles, SVG foreignObject → canvas; no permission prompt, images become neutral boxes). Tab capture (src/screenshot.ts, getDisplayMedia → createImageBitmap → OffscreenCanvas) is opt-in from the bar's camera button and prompts once per page load. Capture is best-effort and bounded (4 s): wherever the environment cannot capture, the pin ships with structured capture alone.

The toolbar's copies of the ws-protocol.ts wire constants are mirrored, not imported (core is a type-only dependency here — that is what keeps the runtime zero-dependency, and fallow's allowTypeOnly enforces it). e2e/toolbar.test.ts drives this transport against a real pinbox serve, so drift between the two copies fails the build.

demo/ is the manual harness: bun run demo starts a hub plus a fixture page and prints the URL. Its README carries the checklist the automated tests cannot cover (real canvas pixels, a real browser).

This package deliberately declares no engines field, and must not gain one. It is the sole exception to the repo-wide "engines": { "bun": ">=1.3.0" } rule. The toolbar is browser code: Bun builds it here, but consumers install it into their own toolchain — a Vite/React app on npm under Node, most often. Declaring a Bun engine would make those installs warn, and refuse outright under engine-strict, for a package that never executes on a server runtime at all.

Key behaviors (see spec): targeting adapters (dom hit-testing | anchor attributes for sandboxed-iframe hosts), a viewport-space overlay re-laid out per frame so pins hold their element through scrolling, sticky headers and inner scroll containers, two pin kinds on the draft (Ask agent / Note — a comment pin is never routed to an agent), pending stages (THINKING → WAITING FOR AGENT → NO RESPONSE with Nudge), resolve/unresolve from the inbox with PR-linked pins grouped, one action table behind the bar, the puck's fan menu and the keyboard (capture-phase, modifier-safe, shortcuts config), a draggable bar and puck, live status badges over WS with the accept/reopen verification step, localStorage offline mirror with reconnect reconciliation, copy-as-markdown fallback, Pinbox.init({ getToken }) auth passthrough — the toolbar never renders a login.