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

@fldr/scratch

v0.1.3

Published

User feedback toolbar for apps — mark what's confusing, unlock with three circles or Cmd/Ctrl+Shift+U

Readme

@fldr/scratch

User feedback toolbar for apps — mark what's confusing. Always on in development; in production, unlock by drawing three circles or pressing Cmd/Ctrl+Shift+U.

Heavily inspired by / derived from Agentation by Benji Taylor (see NOTICE / UPSTREAM_LICENSE). Dom context is still captured for developers and coding agents.

Live playground: scratch.fldr.zip

Install

npm i @fldr/scratch

Usage

import { ScratchFeedback } from '@fldr/scratch';

// Vite
<ScratchFeedback isDevelopment={import.meta.env.DEV} />

// Next.js
<ScratchFeedback isDevelopment={process.env.NODE_ENV === 'development'} />

Props like onSubmit, copyToClipboard, webhookUrl, etc. are forwarded to the toolbar.

Host-managed keyboard shortcuts

Scratch uses browser keydown listeners by default. A host application can instead register every Scratch command with its own shortcut system:

import type {
  ScratchHotkeyAdapter,
  ScratchMode,
} from "@fldr/scratch";

const hotkeys: ScratchHotkeyAdapter = {
  register(command) {
    return appShortcuts.register({
      id: `scratch.${command.id}`,
      binding: command.binding,
      title: command.title,
      description: command.description,
      group: command.group,
      enabled: command.enabled,
      ignoreInputs: command.ignoreInputs,
      preventDefault: command.preventDefault,
      run: command.run,
    });
  },
};

function Feedback() {
  const handleModeChange = (mode: ScratchMode) => {
    // Suspend conflicting app shortcuts while mode === "annotating".
  };

  return (
    <ScratchFeedback
      hotkeys={hotkeys}
      onModeChange={handleModeChange}
    />
  );
}

Omit hotkeys to keep the built-in listeners, or pass false to disable all keyboard shortcuts. Use hotkeyBindings to replace or disable individual bindings. Scratch never installs native keyboard listeners when a host adapter is supplied.

Mail-back feedback

When the user opens the mail icon and confirms send, Scratch delivers to every configured destination:

| Prop | Role | | --- | --- | | feedbackUrl | POST JSON FeedbackMailPayload (full url / pathname / origin / title, context, annotations, markdown, sentAt) | | webhookUrl | Also POSTs the same payload on mail confirm (in addition to existing webhook events) | | mailto | true or an email address — opens a mailto: summary after HTTP posts | | feedbackContext | Merged into payload as context (user id, plan, etc.) | | enableMailFeedback | Force show/hide; defaults to on when any destination above is set |

Also exported: Scratch (always-visible toolbar), FeedbackMailPayload, and useCircleGesture.

Playground

bun install
bun run demo

Opens a local page with feedbackUrl="/api/feedback". Annotate → mail icon → Send; the last JSON payload appears on the page.

Deployed demo: scratch.fldr.zip (bun run deploy).

Discord webhook proxy

Do not place a Discord webhook URL in browser code: its token grants posting access. The standalone Worker in proxy/ stores that URL in KV and exposes an opaque endpoint instead. It is intended to run at feedbackproxy.yet-to-be.com, separately from the playground Worker.

  1. Create a dedicated KV namespace and replace the placeholder WEBHOOKS ID in proxy/wrangler.toml.

  2. Deploy it:

    bun run deploy:proxy
  3. Anyone can register a Discord URL. The raw URL is sent once to the proxy and is not returned:

    curl -X POST https://feedbackproxy.yet-to-be.com/v1/register \
      -H "Content-Type: application/json" \
      --data '{"webhookUrl":"https://discord.com/api/webhooks/..."}'

The response contains an endpoint, such as https://feedbackproxy.yet-to-be.com/v1/<opaque-id>. Use it as webhookUrl in ScratchFeedback; it is the only URL that ships to the browser.

The proxy accepts JSON objects up to 256 KB and accepts any public HTTPS webhook target. Discord webhook URLs are formatted as a safe Discord embed; other targets receive the original JSON payload. Private, loopback, link-local, and credential-bearing URLs are rejected, and redirects are not followed. It never returns or logs the registered URL. Delete the discord-webhook:<id> KV key to revoke an endpoint.

The Worker includes two Cloudflare rate-limit bindings: 10 registrations per IP per minute and 60 deliveries per IP-and-endpoint per minute. Choose unique binding namespace IDs for the account, and add a zone-level WAF rule as a stricter global ceiling—Worker rate-limit bindings are location-local and eventually consistent. Cloudflare's Worker rate-limit documentation describes those limits and their tradeoffs.