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

@tecsteps/kurviq-toolbelt

v0.6.0

Published

Render, compare and evaluate SVG deterministically. The agent-facing companion to kurviq.

Readme

@tecsteps/kurviq-toolbelt

Deterministic rendering, comparison and evaluation of SVG, the agent-facing companion to kurviq.

kurviq itself stays zero-dependency and unchanged. This package is separate because rendering needs a real rasteriser, and an agent needs one that is pinned and reproducible.

Why

A converter that cannot answer questions about its own output forces every caller to build a QA harness. Building Kurviq v1.2 did exactly that, and the harness found five defects that reading the SVG had missed, including one pass that rendered every image black and another that rendered blank.

Install

npm i @tecsteps/kurviq-toolbelt

Use

const { evaluate } = require('@tecsteps/kurviq-toolbelt');

// Authoring: no reference needed.
const { report, artifacts } = evaluate({ svg, width: 512 });

// Verification: compare against the source raster.
const check = evaluate({ svg, referencePng });
check.report.comparison.exactRgba;      // render-identical?
check.report.comparison.changedBounds;  // where it changed
check.report.comparison.perceptual;     // versioned SSIM/DSSIM
kurviq-toolbelt evaluate --svg out.svg --reference in.png \
  --report evaluation.json --render rendered.png

Batch: a directory of traces against a directory of sources

kurviq-toolbelt evaluate \
  --candidate-dir out/ --reference-dir src/ \
  --report-dir reports/ --fail-under-ssim 0.94
PASS  0.9922  flat-a
PASS  0.9912  flat-b
FAIL  0.1438  noisy  (ssim 0.1438 < 0.94)
2/3 passed; kept 1117 B of SVG against 4023 B of reference

Candidates pair with references by basename, so out/logo.svg matches src/logo.png. --manifest pairs.json takes explicit pairs instead:

[{ "name": "logo", "candidate": "out/logo.svg", "reference": "src/logo.png" }]

--report-dir writes one report per asset plus a summary.json carrying every row and the totals, including how many bytes of SVG the passing assets would replace. Without it the summary goes to stdout.

A candidate with no reference is an error for that asset, not a silent skip, and the run still evaluates everything else. Errors exit 2; gate breaches exit 3, so "the tool could not run" stays distinguishable from "the traces are not good enough".

Use it as a quality gate

evaluate used to exit 0 no matter how bad the result was, so every CI or agent use needed a wrapper that parsed the JSON and compared a number. It does not:

kurviq-toolbelt evaluate --svg out.svg --reference in.png --fail-under-ssim 0.94
# ssim 0.9770 dssim 0.0115  1400x800  19342 B svg  changed 98.2%
echo $?   # 0 pass, 3 gate breached

| Flag | Effect | |---|---| | --fail-under-ssim <n> | Exit 3 when perceptual.ssim is below n | | --fail-on-dimension-mismatch | Exit 3 when the sizes differ | | --quiet | Suppress the summary line |

Exit codes are 0 pass, 1 usage error, 2 input refused, 3 quality gate breached. They are distinct so a pipeline can tell "the tool could not run" from "the trace is not good enough".

The one-line summary goes to stderr, so a report piped from stdout stays parseable:

kurviq-toolbelt evaluate --svg out.svg --reference in.png | jq .comparison.perceptual.ssim

Craft instruments

The rest of this package answers correctness questions, and that vocabulary comes from tracing, where a reference raster always exists. Authoring from imagination has no reference, so it goes inert. These answer "does this look right, and by how much is it wrong".

const { inkCentroid, contribution, zoom, contrast } = require('@tecsteps/kurviq-toolbelt');

// Which way does the highlight actually point? Compare it to your light.
inkCentroid(svg, { x: 40, y: 40, width: 120, height: 120 }, { width: 800 }).angle;

// Do the light shafts earn their bytes?
contribution(svg, 'shafts', { width: 800 }).negligible;   // true => they do not

// Look closely, without editing the viewBox.
zoom(svg, { x: 200, y: 120, width: 80, height: 80 }, { scale: 6 });

// Is the label readable on that busy background?
contrast(svg, labelBox, backgroundBox, { width: 800 }).passesAA;

An arm of a controlled experiment that had no library beat the arm that had one, and this is why: it built these instruments and measured its way to a better drawing, finding its planet terminators were 31 to 42 degrees off. The other arm tuned by eye.

Fonts

<text> renders to nothing unless you supply fonts, and system fonts are never loaded because they would make renders machine-dependent. Pass TTF/OTF:

evaluate({ svg, width: 800, fonts: ['./Inter.ttf'] });

Without them a document containing text raises FONT_UNAVAILABLE rather than painting a blank where the labels should be. woff2 raises UNSUPPORTED_FONT_FORMAT, because the renderer would otherwise accept it and render nothing.

Guarantees

  • Deterministic. System fonts are never loaded; external resources are refused rather than fetched; the renderer name and version are in every report.
  • Honest about size. A size mismatch is reported. Nothing is cropped or padded to manufacture a comparison.
  • Honest about alpha. Hashes cover RGBA, so a transparent pixel never collides with a white one. Alpha is compared separately from colour.
  • Honest about scale. SSIM is null, never 1.0, when the image is smaller than the 8×8 window.
  • Stable schema. comparison carries the same keys whether or not the dimensions matched, with null where nothing was measured. Reading comparison.perceptual.ssim never throws. A machine consumer needs a predictable shape more than a compact one.
  • Honest about mean absolute error. Removing a background changes alpha across the whole canvas, which moves MAE by roughly 90x against an opaque reference while the two images stay visually near-identical. When that happens the report sets meanAbsoluteErrorNote telling you to use perceptual.ssim instead, rather than letting the obvious scalar read as a broken trace.
  • Honest about time. mode: "resvg-static" renders one frame with the animation clock unstarted. It proves the static/fallback frame is correct and says nothing about whether the document animates.

Verbs

| Verb | Purpose | | --- | --- | | evaluate(input) | render, optionally compare, optionally verify states; returns a report plus the rendered PNG | | renderStatic(svg, opts) | one deterministic frame: no system fonts, external references refused | | compareRgba(a, b, opts) | exact and perceptual comparison; size mismatch reported, never patched | | compileStateDocument(svg, state) | make :hover / :target / reduced-motion apply in a static renderer | | validateTarget(svg, profile) | check a document against a measured support matrix | | detectFeatures(svg) | which matrix features the document actually uses |

State verification

resvg has no interaction model, so a state is compiled into a static document, the pseudo-class is stripped, other states are dropped, and then rendered and diffed:

const { report } = evaluate({ svg, width: 400, states: ['base', 'hover'] });
report.states.hover.visiblyDiffers;  // false means the rule paints nothing
report.states.hover.changedBounds;   // where it changed

This proves the state's declarations produce the intended pixels. It does not prove a browser delivers the event, that the hit area is right, or that focus order is sane.

Measured target facts

Established by rendering here, not read from documentation. @resvg/resvg-js 2.6.2: CSS custom properties render black (fallback ignored); CSS and SMIL animation never execute; pathLength is ignored at every value; mask-type: alpha falls back to luminance; there is no interaction model. Browser columns are marked assumed because we have no browser oracle yet , claiming otherwise would be the dishonesty the matrix exists to prevent.

Not in this release

Time/animation execution (an AnimationPlan can be sampled, but only a browser can prove it runs), heatmaps, policy budgets, candidate search, and full provenance receipts. See specs/kurviq/CONCEPT-00-AGENT-TOOLBELT.md.

License

MIT