SceneProof turns visual work into a loop a coding agent can follow. It renders your React components and Three.js scenes straight from source, then pairs the image with the structure and context behind it. The agent can see what changed, understand why, and keep working instead of guessing from code.

Install

bun add --global github:ReyJ94/SceneProof
sceneproof --help

SceneProof needs Bun 1.3.14+ and a local Chrome or Chromium. If setup gets fussy, jump to troubleshooting.

SceneProof also ships with a SKILL.md for any agentic harness that supports skills.

Try it

sceneproof render src/components/DemoCard.tsx \
  dom:demo-card \
  --export DemoCard \
  --props fixtures/demo-card.json \
  --scale 4 \
  --out artifacts/demo-card.png

Swap in your own component and props file. SceneProof renders it fresh from source at the scale you asked for—not as a crop of an old screenshot. From there, tree shows the structure, scout helps with Three.js cameras, and the React and Three.js guides cover the rest.

Why agents use it

  • You can trace the picture back to the code. React and Three.js trees keep stable IDs, bounds, styles, materials, lights, and cameras alongside the render.
  • You fix the view before buying more pixels. Context renders, fresh region renders, and Scout camera candidates make framing problems obvious.
  • You get facts, not a made-up verdict. SceneProof reports what ran, what it rendered, how it got there, and what it measured. The agent still judges the result.
  • References stay auditable. Silhouette, luminance, and pixel-probe deltas come with the mask and overlays needed to check that SceneProof compared the right subject.
  • Different kinds of evidence can still travel together. A labeled sheet can hold a context render, a focused detail, a reference, and a before frame without pretending they are one kind of test.
  • The graphics backend is never a mystery. Every Three.js render names the WebGL or WebGPU path and adapter it actually used. WebGPU fails loudly rather than quietly falling back.

What's new in v0.8.0

This release closes the visual review loop for animated and post-processed Three.js work. Continuous timelines emit saved frames, lossless APNG, a compact contact sheet, and a motion map; temporal matrices compare both time and state. Fixtures can own a custom draw pipeline such as EffectComposer without being double-rendered. Delivery review pairs the literal shipping view with fresh detail, default artifact paths no longer overwrite evidence, and doctor detects stale installed binaries.

Earlier releases

v0.7.0 — Added fixture-owned React context, explicit aliases and CSS, neutral state matrices, mixed-evidence sheets, and compact factual briefings.

v0.6.0 — Added explicit perspective and orthographic evidence cameras, auditable seed-assisted masks, localized silhouette differences, and one sheet that keeps every supplied reference view visible.

v0.5.0 — Added explicit WebGL and WebGPU rendering, backend and adapter reporting, and clear failures for silent fallback or incompatible GLSL-only materials.

v0.4.0 — Separated execution from visual acceptance, added typed React prop fixtures, context pairs, delivery-scale checks, motion evidence, and fitted silhouettes.

v0.3.0 — Added supplied-reference comparison, silhouette and luminance deltas, exact pixel probes, scalar sweeps, and labeled 3D reference views.

The workflow

Verb Purpose
tree See the semantic structure
node Inspect one target and its immediate relationships
props Derive a typed JSON starting point for React props
matrix Put labeled variants into one contact sheet
sheet Collect labeled PNG evidence from any workflow
inspect Rebuild the source and save the full scene artifact
scout Compare useful Three.js camera candidates
render Render a target or its context fresh from source
render-region Rerender one exact viewport patch
doctor Check Chromium, WebGL, WebGPU, and local permissions

SceneProof handles the renderer and saves the evidence. The agent chooses the real source, state, target, and view that matter for the job.

React quick path

Point any command at a named export and give it deterministic JSON props. SceneProof picks up source CSS, workspace @/ aliases, and Tailwind v4 too:

sceneproof tree src/components/DemoCard.tsx --export DemoCard --props fixtures/demo-card.json
sceneproof node src/components/DemoCard.tsx dom:demo-card --export DemoCard --props fixtures/demo-card.json
sceneproof render src/components/DemoCard.tsx dom:demo-card --export DemoCard --props fixtures/demo-card.json --scale 4 --out artifacts/demo-card.png

Don't have props for a typed production component yet? Let SceneProof sketch the JSON instead of reverse-engineering the type by hand:

sceneproof props src/components/PricingPanel.tsx --export PricingPanel --out fixtures/pricing-panel.json

--partial-props fills the missing paths with clearly labeled placeholders. The report tells you exactly what it synthesized, so those values can't be mistaken for real state. render-region renders a fresh viewport patch at device scale instead of cropping an existing image.

If the component needs providers, wrapper markup, or an ancestor theme class, put that setup in a fixture instead of changing the production component:

import { defineReactFixture } from "sceneproof/react";
import { AccountPanel } from "@/components/account-panel";
import { AppShell } from "@/components/app-shell";

export const accountPanelFixture = defineReactFixture({
  document: { html: { classes: ["dark"] } },
  render: (props) => (
    <AppShell>
      <AccountPanel {...props} />
    </AppShell>
  ),
});

If an integration doesn't belong in a browser bundle, alias it to an explicit local stub. SceneProof records every substitution without touching application source:

sceneproof render scripts/sceneproof/account-panel.scene.tsx dom:account-panel \
  --export accountPanelFixture \
  --alias @auth/server=./scripts/sceneproof/auth-stub.ts \
  --css src/styles/globals.css \
  --css src/styles/app-shell.css \
  --out artifacts/account-panel.png

Each stylesheet resolves relative imports from its own directory. Repeat --css in the order you want the cascade applied.

Use a matrix when you want to compare whole states side by side instead of squeezing the design into one scalar sweep:

sceneproof matrix scripts/sceneproof/account-panel.scene.tsx dom:account-panel \
  --variants scripts/sceneproof/fixtures/account-panel-variants.json \
  --out artifacts/account-panel-matrix

Each variant can change several nested props together. If the React value is a sealed module constant, the manifest can use a checksum-guarded sourceOverlays replacement instead. SceneProof applies it only inside the browser bundle, requires one exact match, records it in provenance, and never writes it back to the worktree. Three.js matrices currently vary fixture props only. The older scalar --sweep flags still work for compatibility, but they stay out of the main help surface.

Sometimes the useful evidence doesn't belong to one matrix: the whole panel, a fresh 4× detail render, the earlier version, and a supplied reference. Put those artifacts into one labeled sheet instead of opening them from memory:

sceneproof sheet \
  --item context=artifacts/account-panel.png \
  --item detail@4x=artifacts/account-panel-detail.png \
  --item before=artifacts/account-panel-before.png \
  --item reference=references/account-panel.png \
  --out artifacts/account-panel-review

sheet doesn't rerender, choose a camera, or rank the images. It records the path, dimensions, byte size, and SHA-256 of every input, then packages them in the order you gave it. Use matrix for source variants and scout for Three.js camera discovery. Add --compare only when adjacent frames are actually comparable; a pixel delta between a context shot and a detail shot is just noise.

Three.js quick path

The factory can have any export name. --renderer auto recognizes Three.js by its { scene, camera } return value, or you can mark it explicitly with defineThreeFixture from sceneproof/three. Start with structure. If the right camera isn't obvious, let Scout lay out the useful candidates:

sceneproof node scene.ts three:featured-model --export createGalleryScene --props fixtures/featured.json
sceneproof scout scene.ts three:featured-model --export createGalleryScene --props fixtures/featured.json --out artifacts/gallery-scout

Scout returns four useful views: context keeps the source composition, sourceDetail rerenders a region fresh from source, detail gets close to the target, and shape tries another angle. Fix the framing first. Raise --scale only when the view is already useful and raster detail is the remaining limit.

When a supplied reference calls for another projection, use --projection perspective|orthographic. fit keeps the target inside the frame; fill moves in and allows controlled clipping. Actions and timeline frames stay inside one real scene lifecycle:

sceneproof render scene.ts three:featured-model --export createGalleryScene --props fixtures/featured.json \
  --action highlight --frames before,0,80,160,settled --framing source --out artifacts/highlight-transition.png

sceneproof render scene.ts three:featured-model --export createGalleryScene --props fixtures/featured.json \
  --action highlight --frames 0..3000@100ms --framing source --out artifacts/highlight-motion

Comma-separated frames are sparse checkpoints. A numeric range is continuous motion evidence: it writes raw PNGs under frames/, motion.apng, a maximum 12-frame contact sheet, motion-map.png, and frames.json. Use render-region --frames for the same lifecycle over a logical viewport patch.

For shipping-scale claims, keep the delivery camera honest while still getting inspectable detail:

sceneproof render scene.ts three:featured-model --export createGalleryScene \
  --delivery-review 24 --out artifacts/featured-delivery

When --out is omitted, visual commands allocate a unique path under artifacts/sceneproof/; they never reuse a hidden latest target.

--context-pair captures the target alone and in its surrounding scene without rebuilding the fixture. That makes it harder to approve a form against an empty background it will never ship with. The Three.js fixture protocol covers lifecycle details, instance IDs, and deeper diagnostics.

WebGL is the default. Request WebGPU explicitly with --three-backend webgpu when the source supports it. SceneProof reports the backend and adapter it actually used, and fails rather than quietly falling back to WebGL2. See Execution diagnostics for the compatibility details.

How SceneProof reports a result

SceneProof isn't a model, and it doesn't know what “good” means for your task. Its report sticks to what the harness can actually establish:

  • Execution tells you whether the command finished.
  • Artifacts tells you what to open.
  • Provenance tracks the source, fixture state, CSS, document context, aliases, and in-memory source overlays behind the result.
  • Facts and warnings cover things SceneProof can measure or observe: bounds, coverage, cameras, renderers, pixels, motion, comparisons, and limitations.
  • Assertions pass or fail only when you explicitly ask for a mechanical check such as delivery height or visible change.
  • Review reminds the agent that the final visual call still requires looking at the artifact.
{
  "execution": { "status": "succeeded", "meaning": "command-execution-only" },
  "artifacts": { "primary": { "kind": "render", "path": "/tmp/card.png" } },
  "facts": { "target": { "id": "dom:card" } },
  "review": {
    "required": true,
    "decisionOwner": "agent",
    "message": "Open the artifact before making a visual claim."
  }
}

The default output is short enough for an agent loop. Add --json when you want the full factual report. SceneProof no longer exposes the old global judgeability fields, automatic aesthetic ranking, preset review questions, or automatic demands for a reference.

With --reference, SceneProof also writes an aligned silhouette overlay, an amplified difference map, a candidate mask, paired luminance histograms, and any repeatable --probe x,y samples you asked for. Check that the overlay sits on the intended subject before trusting the numbers. A --reference-set keeps several labeled views separate, with their own camera and mask, so an aggregate can't hide a bad perspective.

Keep fixtures honest

If the product already exports the real visual boundary, use it. When you need deterministic setup, keep that setup outside application code:

  • reusable inspectors in scripts/sceneproof/<surface>.scene.ts;
  • fixture data in scripts/sceneproof/fixtures/;
  • one-off investigations in /tmp/sceneproof-inspectors/.

An inspector can import the production owner unchanged and drive it with real props and actions. It shouldn't copy geometry, invent state inside src, or guess how the app probably looks. A fixture proves the current code under the state you declared; it doesn't prove parity with a live session you never recorded. If loading the real boundary would require faking the behavior under test, stop there instead of building a convincing lookalike.

What works today

SceneProof currently handles:

  • TypeScript and JavaScript source entries;
  • React DOM, computed styles, semantic roles, SVG subtrees, and fresh region renders;
  • fixture-owned wrappers and document context, explicit module aliases, per-file CSS resolution, workspace @/ imports, JSON props, and Tailwind v4;
  • labeled multi-parameter matrices and guarded in-memory React source overlays;
  • labeled cross-workflow evidence sheets with input-byte fingerprints and opt-in adjacent raster comparison;
  • full Three.js scene graphs, including transforms, bounds, geometry, materials, uniforms, textures, lights, and cameras;
  • explicit WebGL and WebGPU capture with strict compatibility checks;
  • custom factory names, deterministic props, actions and time, plus stable InstancedMesh IDs;
  • reference/current/difference evidence with silhouettes, luminance, pixel probes, and auditable masks;
  • typed React prop skeletons, including clearly marked partial completion.

SVG-native export isn't here yet. The GitHub install runs SceneProof from its linked source entry; the standalone compiled binary is still experimental for workspace entries with nested imports. WebGPU also depends on the source's own Three.js compatibility—SceneProof won't translate GLSL shaders or WebGL-only addons into TSL for you.

Troubleshooting

Bun isn't installed

On macOS or Linux:

curl -fsSL https://bun.com/install | bash

On Windows PowerShell:

powershell -c "irm bun.sh/install.ps1|iex"

Open a new terminal, check bun --version, then run the SceneProof install command above.

sceneproof isn't on the PATH

Bun puts global commands in ~/.bun/bin. If that directory isn't already on your PATH, add these lines to ~/.zshrc or ~/.bashrc, then open a new terminal:

export BUN_INSTALL="$HOME/.bun"
export PATH="$BUN_INSTALL/bin:$PATH"
SceneProof can't find Chrome
export SCENEPROOF_CHROME_PATH="/path/to/chrome"
sceneproof doctor
An agent sandbox is blocking Chromium

Run sceneproof directly with the agent's unsandboxed/local-render permission. Avoid a compound shell or pipe; it can stop Chromium before SceneProof gets a chance to report the failure.

sceneproof doctor

doctor checks the executable, browser launch, WebGL, a real WebGPU clear-and-readback probe, and the active renderer and adapter. It exits non-zero when a requirement fails. Add --require-backend both when you need both graphics paths.

Development

Run it from source
git clone https://github.com/ReyJ94/SceneProof.git
cd SceneProof
bun install --frozen-lockfile
bun run cli --help
Local quality gate
bun run check

That runs lint, strict TypeScript 7 typechecking, the browser-backed tests, the Bun compiled build, and a compiled typed-props smoke test.

See the changelog for release-level behavior changes.