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
```bash
bun add --global github:ReyJ94/SceneProof
sceneproof --help
```
SceneProof needs [Bun](https://bun.com/docs/installation) 1.4.0+ and a local
Chrome or Chromium. Bun 1.4 runs `playwright-core` natively and `Bun.WebView` drives the installed Chromium over CDP — no `playwright install` download needed. If setup gets fussy, jump to
[troubleshooting](#troubleshooting).
SceneProof also ships with a [`SKILL.md`](skills/sceneproof/SKILL.md) for any
agentic harness that supports skills.
## Try it
```bash
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`](#the-workflow) shows the structure, [`scout`](#threejs-quick-path)
helps with Three.js cameras, and the [React](#react-quick-path) and
[Three.js](#threejs-quick-path) 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:
```bash
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:
```bash
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:
```tsx
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) => (
),
});
```
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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](docs/three-fixtures.md) 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](docs/three-fixtures.md#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.
```json
{
"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/.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:
```bash
curl -fsSL https://bun.com/install | bash
```
On Windows PowerShell:
```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:
```bash
export BUN_INSTALL="$HOME/.bun"
export PATH="$BUN_INSTALL/bin:$PATH"
```
SceneProof can't find Chrome
Bun 1.4's `Bun.WebView` and `playwright-core` auto-detect `BUN_CHROME_PATH`, `SCENEPROOF_CHROME_PATH`, and system binaries (`/usr/bin/chromium` on Omarchy/Arch, `google-chrome`, `chromium-browser`, etc.). No `playwright install` needed when a system Chromium exists.
```bash
export SCENEPROOF_CHROME_PATH="/path/to/chrome"
# or
export BUN_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.
```bash
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
```bash
git clone https://github.com/ReyJ94/SceneProof.git
cd SceneProof
bun install --frozen-lockfile
bun run cli --help
```
Local quality gate
```bash
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](CHANGELOG.md) for release-level behavior changes.
## License
[MIT](LICENSE) © 2026 ReyJ94