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

@prios/feedback-widget

v1.1.0

Published

Plug-and-play feedback widget that opens GitHub issues via the issue-tracker /intake endpoint.

Readme

@prios/feedback-widget

Plug-and-play feedback widget that opens GitHub issues via the issue tracker's /intake endpoint. Framework-agnostic (Web Component + Shadow DOM); one line to integrate.

Features

For the person reporting

  • Category chips: Bug Report, Feature Request, UX Issue, Performance, Content Quality (configurable). The choice becomes the issue's label.
  • Title, with AI help: "✨ Suggest" turns the description, and any title already typed, into a short, specific title such as "Save button spins forever on the checkout page". The reporter reviews it, then clicks Use this or Discard.
  • Description, with AI help: "✨ Improve with AI" rewrites a rough draft into a clear report (summary, steps to reproduce, expected vs. actual). Both AI tools only use what the reporter wrote. They don't invent steps, versions or causes.
  • "Already reported?": while the reporter types, the widget looks for open reports in the same project with a similar title and description, and lists up to 3 with their status. Sending anyway still helps; see grouping below.
  • Screenshots: capture the current screen in one click, upload images, paste a screen grab (Ctrl/⌘+V), or drop an image onto the form. Up to 4 images, 5 MB each, PNG/JPEG/GIF/WebP. The widget hides itself while it captures the screen.
  • Clear error messages when something fails (see Troubleshooting).

For the team receiving it

  • Lands in the issue tracker:
    • For a project with one repo, the report is published straight to GitHub.
    • For a project with several repos, it waits in triage for a PM to route.
  • Similar reports are grouped:
    • A report that closely matches an open issue is held back from GitHub and listed under that issue in the console.
    • A PM either merges it (closed, and its text and screenshots are copied onto the original as a comment) or marks it a different problem (published as its own issue).
  • Context captured automatically: page URL and browser, plus anything your app returns from getContext() (user id, app version…).
  • Controlled from the console:
    • A project's dashboard settings can move the widget's corner, or switch the widget off entirely, without a redeploy.

For the site embedding it

  • One line to add:
    • The whole form lives in a Shadow DOM, so your CSS can't break it and its CSS can't leak into your page.
    • It renders in the browser's top layer, so it shows above your app's own stacking contexts.
  • Spam protection: Cloudflare Turnstile CAPTCHA, a hidden honeypot field, and per-visitor rate limits.
  • Privacy:
    • The similar-reports hint only shows reports your own users filed (through the widget or a shared link), and only their title and status.
    • It never shows report text, the reporter, repo names or GitHub links.
    • Issues created inside the console or directly on GitHub never appear.

Usage

Build-based app (React / Vue / Angular / Svelte / anything):

import { mountFeedbackWidget } from "@prios/feedback-widget";
mountFeedbackWidget({ projectKey: "nolab" });

Static site / no build step:

<script
  src="https://cdn.jsdelivr.net/npm/@prios/feedback-widget@1"
  data-project="nolab"
></script>

projectKey must match a project registered in the issue tracker (see step 2). Keys are lowercase slugs of the project's name — nolab, issue-tracker, prios-portal — and are matched case-insensitively.

Integrating

  1. Install the package.

    npm install @prios/feedback-widget
    # or: yarn add / pnpm add @prios/feedback-widget
  2. Make sure your project is registered. Check yourself — this public endpoint returns only keys and display names, never the underlying repos:

    curl https://devpipeline.prios.no/issue_tracker/api/intake/projects

    If it isn't there, an admin creates it once on the console's Projects page (or with the CLI). The key is derived from the project name ("NOLAB App" → nolab-app) and shown on that page.

  3. Mount it once, near your app's entry point:

    import { mountFeedbackWidget } from "@prios/feedback-widget";
    mountFeedbackWidget({ projectKey: "nolab" });
  4. Call it inside your framework's mount lifecycle, not at module scope — mountFeedbackWidget touches the DOM (document.body), so it needs to run after your app has mounted, and should be torn down with destroy():

    • React — useEffect on mount, w.destroy() in the cleanup function.
    • Vue 3 — onMounted / onUnmounted.
    • Angular — ngOnInit / ngOnDestroy in your root component.
    • Svelte — onMount / onDestroy.
    • Static/no build — skip this step entirely; use the <script data-project> tag instead (see above).
  5. (Optional) Attach app context — whatever getContext() returns rides along with every submission (e.g. logged-in user id, app version). It's called at submit time, so read current state rather than capturing it once:

    mountFeedbackWidget({
      projectKey: "nolab",
      getContext: () => ({ userId: currentUser?.id, appVersion: APP_VERSION }),
    });
  6. Allow it on your domain.

    • The widget's CAPTCHA (Cloudflare Turnstile) only works on hostnames listed on the Turnstile widget in Cloudflare — ask an admin to add yours.
    • If your site sends a Content Security Policy, allow https://challenges.cloudflare.com (script-src, frame-src) and https://devpipeline.prios.no (connect-src). Loading from the CDN also needs https://cdn.jsdelivr.net in script-src.
  7. Verify it end-to-end. Click the trigger, submit a test message, and confirm a new issue shows up in the console dashboard (or the mapped GitHub repo).

Nothing else to configure. apiUrl and turnstileSiteKey are baked into the published package — you only pass projectKey, plus any of the optional props below.

Options

mountFeedbackWidget(config) returns { open(), close(), destroy() }.

| Option | Notes | | --- | --- | | projectKey | Required. Registered project key. | | getContext | () => Record<string, unknown>, captured at submit. | | theme | "dark" (default) or "light". | | position | "bottom-right", "bottom-left", "top-right", "top-left". Normally set per project from the console dashboard; passing it here pins it. | | categories | Category chips; the first is selected by default. | | triggerLabel, triggerSubtitle | Text on the floating button. | | avatars | Up to 3 image URLs stacked on the button. | | maxDescription | Character limit for the description. | | apiUrl, turnstileSiteKey | Baked-in defaults — override only for local testing. |

The script tag takes the same settings as attributes: data-project, data-theme, data-position, data-trigger-subtitle, data-avatars (comma-separated URLs), and — for local testing only — data-api and data-turnstile-site-key.

Types: FeedbackConfig and WidgetHandle are exported from the package.

Integration examples

React — mount once in a top-level effect:

import { useEffect } from "react";
import { mountFeedbackWidget } from "@prios/feedback-widget";

useEffect(() => {
  const w = mountFeedbackWidget({
    projectKey: "nolab",
    getContext: () => ({ userId: userRef.current?.id }),
  });
  return () => w.destroy();
}, []);

Vue 3 — in App.vue:

import { onMounted, onUnmounted } from "vue";
import { mountFeedbackWidget } from "@prios/feedback-widget";

let widget;
onMounted(() => (widget = mountFeedbackWidget({ projectKey: "nolab" })));
onUnmounted(() => widget?.destroy());

Angular — in your root component:

import { Component, OnDestroy, OnInit } from "@angular/core";
import { mountFeedbackWidget, type WidgetHandle } from "@prios/feedback-widget";

@Component({ selector: "app-root", templateUrl: "./app.component.html" })
export class AppComponent implements OnInit, OnDestroy {
  private widget?: WidgetHandle;

  ngOnInit() {
    this.widget = mountFeedbackWidget({ projectKey: "nolab" });
  }

  ngOnDestroy() {
    this.widget?.destroy();
  }
}

Svelte — in App.svelte:

import { onMount, onDestroy } from "svelte";
import { mountFeedbackWidget } from "@prios/feedback-widget";

let widget;
onMount(() => (widget = mountFeedbackWidget({ projectKey: "nolab" })));
onDestroy(() => widget?.destroy());

Static / any site:

<script
  src="https://cdn.jsdelivr.net/npm/@prios/feedback-widget@1"
  data-project="prios-portal"
></script>

Troubleshooting

The widget shows the reason when something fails. The common ones:

| Message | Cause | | --- | --- | | "This feedback form isn't set up correctly (unknown project key)" | projectKey isn't registered — see step 2. | | "Couldn't verify you're human" | Your domain isn't on the Turnstile widget's allowed hostnames, or a CSP blocks challenges.cloudflare.com — see step 6. | | "Couldn't reach the feedback server" | Offline, or a CSP / ad-blocker blocks the API URL. | | "Too many submissions / AI requests" | Per visitor, per minute: 5 submissions, 5 description rewrites, 10 title suggestions. Wait a minute. | | "Screenshots must be PNG, JPEG, GIF or WebP images" | Other formats (e.g. HEIC, SVG) are rejected. | | "The AI assist is unavailable right now" | The server has no AI key configured, or the AI provider is down. The form still works without it. | | "✨ Suggest" fails with "Request failed (404)" | Widget 1.1+ is talking to an older API. Deploy the API first. |

The "Already reported?" hint never shows an error. If the lookup fails, the panel just stays hidden.

Building & publishing

npm install
npm test
FEEDBACK_API_URL=https://devpipeline.prios.no/issue_tracker/api \
FEEDBACK_TURNSTILE_KEY=0x4AAAAAAEey0XGnTTYKFGWl \
npm run build

Outputs dist/feedback-widget.js (ES), .cjs, .iife.js, index.d.ts, and dist/cli/index.js. apiUrl and turnstileSiteKey are baked into the bundle at build time; without the env vars the build falls back to local-dev values (http://localhost:3001, no CAPTCHA) — fine for testing, never for release.

Publishing

Through CI: bump version in package.json, then push a <<<<<<< HEAD widget-v<version> tag (e.g. widget-v1.1.0);

widget-v<version> tag (e.g. widget-v1.0.7);

c58f08797a928d9d83bc3d4656f09c405c24e67b .github/workflows/publish-widget.yml publishes with the production values.

By hand, from feedback-widget/ — pick patch, minor or major to match the change; npm version bumps the version and creates a git tag, and npm publish runs the publish checks and builds the package automatically:

npm version patch
FEEDBACK_API_URL=https://devpipeline.prios.no/issue_tracker/api \
FEEDBACK_TURNSTILE_KEY=0x4AAAAAAEey0XGnTTYKFGWl \
npm publish
git push --follow-tags

Set FEEDBACK_API_URL to the API origin without /intake. The build embeds this URL and the Turnstile site key in the published bundle; they are public client configuration, not secrets. npm publish refuses to run unless FEEDBACK_API_URL is a non-local https URL and FEEDBACK_TURNSTILE_KEY is a real (non-test) site key — that's what stops a local build shipping.

CLI

npx @prios/feedback-widget init <projectKey>
# prints the integration snippet

FEEDBACK_API_URL=https://devpipeline.prios.no/issue_tracker/api \
ADMIN_TOKEN=<an admin's login token> \
npx @prios/feedback-widget register "<Display name>" <repo> [repo…]
# registers a project and prints its key

Versioning

  • Semver. Apps depend on "^1"; CDN users load @1.
  • A breaking change (e.g. a renamed config prop) ships as v2, opted into deliberately.
  • Because the API URL and Turnstile key are baked in at build time, changing either means rebuild + republish — apps pick up the new values on their next update with no code change.
  • jsDelivr caches what @1 resolves to for up to ~12 hours. To pick up a release immediately, pin the exact version (@1.1.0) or purge https://purge.jsdelivr.net/npm/@prios/feedback-widget@1.