@entviz/react — integration guideReact components that render a high-entropy value (a public key, hash, UUID, address, CID, …) as an entviz — a comparable visual fingerprint — and help a person decide whether two values are the same.
This guide is the developer-facing overview: components, props, the onEvent
firehose, theming, and the security contract you inherit by embedding these
components. For the exact generated type signatures see the TypeDoc API reference
(npm run docs at the repo root). For the why behind the design, see
pill-design.md, comparison-design.md,
and the canonical ../../../../entviz/docs/threat-model.md.
The one thing to internalize. entviz protects a single judgment: are these two values equal? A green
=verdict means "equal to the reference you supplied" — never "this reference is the one you should trust." Confidentiality of the value is out of scope (entviz is a comparison aid, not a secrecy primitive); values are assumed public. The components are hardened against a tampering threat — an attacker (or ambient CSS) trying to make two different values look the same — not against a value being observed.
npm install @entviz/react @entviz/core react
@entviz/react ships raw .ts authored with React.createElement (no JSX). It
has one runtime dependency, @entviz/core (isomorphic; no Node built-ins), and
react >= 17 as a peer.
@entviz/react and @entviz/core publish .ts source, not compiled JavaScript
— there is no build step in the packages themselves. Toolchains that already
transpile TypeScript consume them with no extra setup: Vite, Vitest,
Bun, Deno, and Node ≥ 22.6 (native type-stripping). Some bundlers,
though, skip node_modules from TypeScript transpilation by default and must be
told to include these packages:
transpilePackages in next.config.js —
transpilePackages: ["@entviz/react", "@entviz/core"].ts-loader/babel-loader rule usually carries
exclude: /node_modules/; add an include (or narrow the exclude) so
@entviz/* is transpiled.transformIgnorePatterns doesn't exclude @entviz/*.--no-experimental-strip-types): run via a loader
such as tsx, or precompile the packages yourself.If a dependency-side Unexpected token, Cannot use import statement outside a module, or "unexpected .ts" error appears at build time, an untranspiled
@entviz/* package is the cause.
An entviz enters a page as a compact pill, opens to the full visualization, and — deliberately — becomes a comparison. That arc is Cite → Visualize → Compare, and it's one object revealing itself, not three widgets.
| Component | Role |
|---|---|
<Entviz> |
The spec-locked SVG render of one value (optionally with a size/shape/copy toolbar). |
<EntvizPill> |
The collapsed, inline form. Click to expand into <Entviz>; opt into an in-popover "Compare against another value…" that opens <EntvizCompare> in place. |
<EntvizCompare> |
The full comparison surface: acquire another value to compare against yours (paste / drop / click-to-upload / URL), get a machine verdict, or run the guided walk or the voice ceremony. |
<EntvizWalk> / <EntvizVoiceCompare> |
The guided single-user walk and the two-party voice ceremony. Usually reached through <EntvizCompare>; exported for direct use. |
import { Entviz, EntvizPill, EntvizCompare } from "@entviz/react";
// 1. Just render one.
<Entviz value="550e8400-e29b-41d4-a716-446655440000" />
// 2. Inline pill that drills in (Visualize → Compare) when the host opts in.
<EntvizPill value={key} label="signing-key" onCompare={() => log("compare")} />
// 3. The comparison surface directly.
<EntvizCompare value={mine} onVerdict={(v) => log(v.state)} />
Shared render inputs on every component: value (required), targetAr,
fontSizePt, note (≤10 printable-ASCII chars, never hashed). Common chrome:
className, style, locale, onEvent. Only notable/component-specific props
are listed below; see TypeDoc for the full signatures.
<Entviz>| Prop | Type | Notes |
|---|---|---|
controls |
boolean |
Show the size-ladder + shape picker + copy/export kebab beside the figure. |
reshapable |
boolean |
Offer the shape picker (off for a raster reference). |
onResize / onReshape |
(n) => void |
Fired on size/shape change; passing them makes size/shape controlled. |
title |
string |
Accessible label (defaults to a channel description incl. the note). |
onError |
(msg) => void |
Render failure (e.g. an invalid note). |
<EntvizPill>| Prop | Type | Notes |
|---|---|---|
label |
string |
First-party trusted text after the type (unlike the note). |
showType / showIcon |
boolean |
The parser-derived type label / the constant 2×2 badge. |
dir |
"ltr"|"rtl"|"auto" |
Chrome writing direction; the glyph is never mirrored. |
onExpand / onCompare |
() => void |
Entered visualize / entered compare. Providing onCompare opts in the in-popover "Compare against another value…" affordance. |
showCompareAffordance |
boolean |
Keep onCompare telemetry but hide the built-in button. |
open / onOpenChange |
boolean / (open) => void |
Controlled disclosure. With open set, the popover follows it and actions report via onOpenChange (host owns state). Controlling open cannot skip the reference gate. |
onCopy |
(kind) => void |
A copy/export action fired. |
<EntvizCompare>| Prop | Type | Notes |
|---|---|---|
reference |
{ kind: "text"|"svg"; data: string } |
A host-supplied value to compare against (renders directly; no acquisition UI). |
layout |
"side-by-side"|"stacked"|"auto" |
Panel arrangement; "auto" stacks when narrow (mobile). |
onVerdict |
(v: Verdict) => void |
Terminal machine verdict. |
allow |
{ paste?; file?; url?; drop?: boolean } |
Restrict-only, allowlist-closed. Absent ⇒ all methods on. Present ⇒ a method is on only if its key is exactly true (so {paste:true} disables file/url/drop). Fails closed. |
fetchReference |
(url, {origin, signal}) => Promise<{text}|{blob}> |
Host fetcher (proxy/auth/CORS). Its bytes run the same §6.2 gauntlet as pasted bytes — it supplies bytes, never a verdict, and can't mark a reference identical. Origin is still shown before any fetch. |
includeContent |
boolean (default false) |
When true, reference.acquired events carry the raw content. A convenience for logging (values are public); off by default only so a pasted-secret corner case isn't ambiently logged. |
rng |
() => number |
Deterministic sampling for tests/demos. Ignored in production (see Security). |
messages |
Partial<CompareMessages> |
Localize surrounding chrome. Verdict/scoping/provenance strings are not override-able (see Security). |
<EntvizWalk> / <EntvizVoiceCompare> add mode, externalFigures, onStep,
onComplete, and rng.
onEvent firehoseEvery component accepts onEvent?: (e: EntvizEvent) => void — one typed,
notify-only firehose that mirrors the whole lifecycle and comparison journey,
in addition to the specific callbacks (onVerdict, onExpand, …). Use it for
logging, telemetry, analytics, and custom affordances without forking.
<EntvizCompare value={mine} onEvent={(e) => {
// e.type is a discriminant; e.seq (monotonic), e.ts, e.source, e.sensitivity
if (e.type === "verdict.change") analytics.track("entviz_verdict", { state: e.verdict, provenance: e.provenance });
if (e.type === "fetch.error") log.warn("reference fetch failed", e.origin, e.message);
}} />
EntvizEvent is a discriminated union (type + an object payload). The kinds:
| Stage | Event types |
|---|---|
| lifecycle | render.error, disclosure.change, copy |
| display | display.resize, display.reshape, display.tab |
| acquisition | reference.acquired, reference.cleared, reference.mediumDetected, reference.readError, secret.detected |
| fetch | fetch.start, fetch.success, fetch.error |
| outcome | verdict.change |
| verification | walk.start, walk.step, walk.complete, voice.start, voice.complete |
Every event carries seq (monotonic per instance), ts, source
("entviz"|"pill"|"compare"|"walk"|"voice"), and a sensitivity routing hint
("plain"|"network"|"content" — a hint for your logging, not a security
boundary). Three invariants are structural, not conventions:
verdict.change (and every verification event) is notify-only. A host
handler cannot veto, delay, or synthesize a verdict. Only fetch.start is
advisory-cancelable — call its preventDefault() to block a fetch (fail-closed:
it can only deny egress, never force a pass).voice.step, and no *.step fires during a live ceremony. The
live authenticator-selected cell order must never leave the endpoint (it would
let a remote party pre-position). walk.step (single-user machine walk only)
carries a feature kind + index, never glyphs.onEvent can't wedge the component — the call is wrapped; a bug
in your handler never skips a safety or breaks rendering.The components ship no fonts and no color scheme of their own. They inherit
the host's font-family and color, derive surfaces from currentColor, and
expose --entviz-* CSS custom properties for the rest — so the same component
looks native in a light SaaS, a dark dashboard, a serif site, etc. Set the vars on
any ancestor:
.my-app {
--entviz-compare-action: #2f6bff; /* accent for buttons/tabs */
--entviz-pill-popover-bg: #1a1f2b; /* dark popover surface */
/* …see TypeDoc / source for the full --entviz-* set */
}
Security note — some colors are deliberately NOT themeable. The verdict chip color + its
=/≠symbol and the walk focus ring/scrim are fixed literals, not driven by--entviz-*vars. This is by design: an attacker who controls ambient CSS (threat-model tier T2) must not be able to set--entviz-compare-bad: greenand repaint a "Different" verdict, or set the walk ring transparent and erase the attention spotlight. Everything else (menus, pill chrome, radii, action accents) is freely themeable.
Embedding these components, you get these guarantees against a false-"same" attacker (and you should not try to defeat them):
=/green appears only for a machine
identical. You cannot relabel a verdict via messages (the verdict,
scoping, and provenance strings are pinned), recolor it via CSS vars (fixed
literals), or force/suppress it via onEvent (notify-only).= is scoped. The "equal to THIS reference, not that it's
trustworthy" caveat renders on the machine chip too — including under controlled
open. Reference provenance is always shown.rng is ignored in production
(NODE_ENV==='production' → platform CSPRNG), so a predictable check order can't
be shipped to let an attacker pre-forge the sampled cells.fetchReference result and
any URL-fetched body run the same closed-profile → recompute → re-render →
self-consistency gauntlet as pasted bytes; a host fetcher can't hand back a
pre-blessed identical.allow, allowlist-closed) for host DLP/policy
— e.g. disable URL-fetch. Note: generic SSRF from your own fetchReference is
your app's responsibility, outside entviz's asset frame.What's explicitly out of scope: confidentiality of the value (assume it's public — event payloads may carry it), clipboard/paste tampering, and defending a user who chooses not to look.
Pass locale (a BCP-47 tag; 18 locales ship, English fallback) and/or messages
to localize chrome. RTL locales mirror the chrome, never the glyph. The value,
comparison text, and verdict semantics are never localized or case-folded — they
are exact codepoints, and the verdict strings are not host-override-able.
pill-design.md — the collapsed form + copy affordances.comparison-design.md — the comparison surface, the
verdict machine, the guided walk (§14) and voice ceremony (§15).../../reviews/integration-surface/proposal-2026-07-02-v2.md — the events/props
design rationale and its adversarial security review.entviz repo.