---
name: rnx-visual
description: Pixel-level rendering comparison between Peach and native iOS — fonts, gradients, shadows, image fidelity
---

# Peach visual fidelity

pixel-level rendering fidelity: does the Peach canvas match native iOS
for text, gradients, shadows, blur, and image rendering? the `rnx`
CLI gives you two primitives — **canvas capture** (`rnx screenshot`)
and **structural diff** (`rnx debug snapshot`/`diff`). pixel
comparison itself is "capture both sides, diff the PNGs with a standard
image tool" — there is no single oracle-diff command, and that's fine:
the capture step is the hard part and it's fully reproducible.

> needs a connected, pinned sim. if `rnx describe` errors,
> load `/rnx-setup` first.

## route in

- structural change ("which nodes / which props changed?") → load
  `/rnx-debug`, debugging branch — `debug snapshot` + `debug
  diff` is faster, deterministic, and tells you *why*.
- visual change (color, gradient, shadow, font metric, blur, image)? →
  continue here.
- comparing Peach to your iOS simulator for fidelity? → continue.
- regressing your own app against itself across commits? → continue.

## anti-patterns (read first)

- **run structural diff before pixel work.** `rnx debug snapshot` /
  `debug diff` (in `/rnx-debug`) is cheaper, deterministic, and
  points at the responsible nodes. only when it's empty (or the change is
  pure rendering) does a pixel comparison add information.
- **don't single-screenshot animated surfaces.** a gradient
  mid-animation never matches. gate the capture behind `rnx wait
  idle` first.
- **a > 5% pixel threshold hides regressions.** ~1% is the right
  ballpark for catching real font / shadow / gradient drift without
  flagging anti-alias noise. go higher only with a written reason.
- **pin device and theme.** capture both sides on the same simulated
  device and appearance. capture each appearance separately with
  `rnx screenshot` after setting it on both simulators.
- **commit baselines alongside the code change that produced them.**
  baseline churn without code churn means someone captured noise.

## capturing — `rnx screenshot`

fast, no extra setup, works on any loaded app:

```sh
rnx screenshot --output app.png            # full canvas
rnx screenshot --area 0,200,393,400        # crop to a logical rect
rnx screenshot --id loginButton -o btn.png # crop to a node by testID
rnx screenshot --text "Sign in"            # crop to a node by text
rnx screenshot --no-shell -o tenant.png    # tenant surface only, no iOS chrome
rnx screenshot --shell-only                # only the simulated iOS chrome
```

`rnx screenshot` captures the current screen as a PNG. navigate to each
screen you want to inspect and capture it with a distinct output filename.

## comparing pixels

capture both sides to PNGs, then diff with any standard image tool.

**Peach vs Peach (regression across your own commits):**

```sh
rnx screenshot -o before.png                      # baseline, before your change
# … make the rendering change, let the sim hot-reload …
rnx screenshot -o after.png                       # after your change
npx pixelmatch before.png after.png diff.png 0.1      # writes diff.png
# or: magick compare before.png after.png diff.png
```

**Peach vs native iOS (fidelity to the real thing):**

```sh
# native side: lifecycle and UI use xcodebuildmcp with one claimed simulator
# lossless capture names that simulator explicitly; never use the booted alias
xcrun simctl io <udid> screenshot --type=png native.png
# rnx side: same screen, same appearance
rnx screenshot -o rnx.png
npx pixelmatch native.png rnx.png diff.png 0.1
```

`xcodebuildmcp simulator screenshot` may return an optimized JPEG even when the
proof contract requires a lossless source. Use it only when JPEG is acceptable.
For parity proof, use the explicit-UDID PNG command above, then verify the
decoded dimensions and file type before admitting the capture.

crop both to the same region (`--area` / `--id`) when you only care
about one element — a full-screen diff buries a 3px font-baseline shift
under unrelated chrome.

## typical workflow

1. structural diff first — confirm the change is genuinely visual.
2. capture the affected screen with `rnx screenshot` (crop to the
   element if you can).
3. diff against the baseline (previous commit, or a native capture).
4. iterate the rendering until the diff is under threshold.
5. navigate to each affected screen, capture it, and inspect the PNG
   for unexpected rendering changes.

## you're done when

- the diff is under threshold on every affected case, **or**
- the differences are explicitly accepted, baselines re-captured, and
  both halves (code + baselines) committed together.
- the captures show no unexpected changes across all affected screens.

## recovery — common failure modes

- **a single icon/glyph diffs** — font-load race or image not yet
  cached. recapture after `rnx wait idle`, or navigate away and back
  to seed the cache.
- **everything diffs by a constant offset** — you captured the two sides
  on different devices or appearances. pin both, recapture.
- **baselines churn without a code change** — someone captured noise (a
  lingering badge, half-open keyboard, a drifting clock). revert and
  recapture from a clean, idle state.
- **persistent delta on the same font / shadow / gradient across
  unrelated screens** — that's an engine-level rendering fidelity issue,
  not your app. report it with the affected captures attached.
