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

@voxket-ai/qa-reporter

v0.1.1

Published

QA-only bug and feedback reporter for Voxket frontends. Tree-shaken out of production builds.

Readme

@voxket-ai/qa-reporter

QA-only bug and feedback reporter for Voxket frontends. Captures the context that makes a report actionable — the failing request, the console error, and the source file of the clicked component — and posts it to voxket-sentinel.

This package must never run in production. That is enforced by the build, not by a runtime flag. See below.


How the production guarantee works

initQaReporter() is safe to call unconditionally from app layout code. In a production build it compiles down to a function that returns a no-op handle:

var e={enabled:!1,open(){},close(){},destroy(){}};
async function o(r){return e}

That is the entire production entry — ~660 bytes, with the capture runtime and the whole widget UI nowhere in it.

Three things combine to make that true:

  1. The gate is a single comparison against process.env.NEXT_PUBLIC_VOXKET_ENV. Next inlines NEXT_PUBLIC_* as string literals at build time, so the comparison folds to a constant and the early return becomes unconditional.
  2. The runtime is reached only through dynamic import(). It is emitted as a separate chunk, so it is never part of the entry the browser downloads.
  3. The folded gate makes that import() unreachable, so the chunk is never fetched. Under webpack the dead branch is detected at parse time and the chunk is not even emitted.

Two rules that keep this working

  • Keep the gate inline and single-term. Both of these broke it during development, and both shipped the runtime to production:
    • && !config.forceEnable — a runtime value makes the condition unfoldable.
    • && process.env.VOXKET_ENV !== "qa" — looks equivalent, but Next only inlines NEXT_PUBLIC_* into client bundles, so the second term stays a runtime lookup.
  • Never statically import the runtime from index.ts.

There is deliberately no forceEnable escape hatch. For local development, set NEXT_PUBLIC_VOXKET_ENV=qa.

test/gate.test.ts asserts all of this against a real production bundle, including a counter-test that the runtime is reachable when the environment is qa — so a gate broken permanently closed fails too.


Usage

npm install @voxket-ai/qa-reporter
// app/layout.tsx
"use client";
import { useEffect } from "react";
import { initQaReporter } from "@voxket-ai/qa-reporter";

useEffect(() => {
  const reporter = initQaReporter({
    endpoint: "https://sentinel.qa.voxket.ai/api/v1/reports",
    product: "voxket-web-2.0",
    ingestToken: process.env.NEXT_PUBLIC_VX_INGEST_TOKEN,
    reporter: { email: session?.user?.email },
    releaseSha: process.env.NEXT_PUBLIC_RELEASE_SHA,
  });
  return () => {
    reporter.then((r) => r.destroy());
  };
}, []);

Set NEXT_PUBLIC_VOXKET_ENV=qa in the QA environment only. ingestToken is the shared QA ingest token: once the service has one configured (as it does in QA), ingest returns 401 without a matching x-voxket-ingest-token header.


The widget

initQaReporter() mounts a floating "Report an issue" trigger. Opening it gives a form for type, severity, title and description, plus "Point at the component", which is the part that makes reports actionable.

The UI lives in a shadow root. That is not cosmetic: this runs inside apps we do not control, so without isolation the host's global CSS restyles our form (Tailwind preflight alone would flatten every control) and our CSS leaks into the app under test — meaning the tool changes the thing it is supposed to be observing.

The picker has three properties worth knowing about, each with a test:

  • It cannot pick itself. Our chrome is hidden for the duration of each hit test rather than filtered out afterwards, because filtering would miss anything the host app renders above us at the same coordinates.
  • The click never reaches the app. Listeners run in the capture phase and stop propagation, so picking a "Delete" button does not delete anything.
  • It is always escapable. Escape, right-click and cancel all tear down, and teardown is idempotent. A picker that traps the page is worse than no picker.

Feature requests cannot be submitted without acceptance criteria. That is enforced at the keyboard, while the person still has the context in their head — a server-side rejection ten seconds later is far too late for them to supply it.

Screenshots

Screenshot capture is injected, not bundled:

initQaReporter({
  endpoint: "...",
  product: "voxket-web-2.0",
  captureScreenshot: async () => {
    const html2canvas = (await import("html2canvas")).default;
    const canvas = await html2canvas(document.body);
    return {
      mimeType: "image/png",
      data: canvas.toDataURL("image/png").split(",")[1],
      width: canvas.width,
      height: canvas.height,
    };
  },
});

Both available approaches have real costs: html2canvas is ~50KB and mis-renders complex CSS, while getDisplayMedia prompts on every use and can capture unrelated tabs. Forcing either on every consumer is worse than letting the app that knows its own rendering decide. Omit the option and the checkbox simply captures nothing — the picker already supplies the component, its computed styles and its source location, which is what actually makes a visual report actionable.

What gets captured

| Field | Why it matters | |---|---| | network with request/response bodies | The strongest FE-vs-BE signal. A 5xx in the trace settles the routing question before a model is involved. | | console errors + stacks | Usually names the failing module directly. | | target.source | file:line:column of the clicked component, stamped by the build plugin. Tells the agent which file to open instead of making it guess. | | target.computedStyles | Makes "the spacing looks wrong" a diffable observation. | | fingerprint | Dedupe key, so five people hitting one broken page produce one ticket. |

Capture is always-on with bounded ring buffers, because bugs get noticed seconds after the failing request — starting capture when someone decides to report is too late.

Credential redaction

Bundles include headers and payloads, get attached to Jira tickets the whole team can read, and are embedded into the cortex knowledge store. So redaction happens in the browser, before anything is transmitted:

  • Sensitive header and body keys (authorization, cookie, *token*, *secret*, session, password, …), matched case-insensitively at any nesting depth.
  • Credential-shaped values regardless of key — bearer tokens, JWTs, and common provider key prefixes.
  • Query-string and userinfo credentials in URLs.

Add redactKeys: ["tenantPin"] for anything domain-specific. Payloads are also depth-limited, cycle-safe and truncated so a bundle cannot balloon on a large response.


Development

npm install
npm test        # includes the production-bundle safety assertions
npm run build
npm run typecheck

Status

Capture spine, widget UI and element picker are all in. target.source is populated once @voxket-ai/qa-source-plugin is applied to the app's build.

Adding the full UI did not change the production guarantee: the entry is still ~660 bytes with no runtime markers, because everything above lives behind the dynamic import.