Capture stable before/after screenshots on an iOS simulator or Android emulator and pixel-diff them into evidence. Use when asked for a visual regression check, a screenshot diff, a before/after visual comparison, or when a visible mobile UI change needs pixel proof — including the executed screenshot-diff that a Tamagui conversion batch requires.
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
screenshot-diff toolscreenshot-diff is a real first-party argent MCP tool, and it is the preferred path for every diff in this skill. Hand-rolling a pixel diff is the fallback (§ 5), not the default.
| Parameter | Required | Notes |
|---|---|---|
udid | yes | iOS UUID or Android adb serial, same as every other argent tool |
outputDir | yes | Where the diff artifacts are written |
baselinePath, currentPath | mode A | Two PNGs you already saved to disk |
captureBaseline, captureCurrent | mode B | The tool captures live from the device instead |
rotation | no | The same rotation parameter screenshot takes |
Supply either the saved-PNG pair or the live-capture flags — the two modes are alternatives, not additions.
Returns { summary, diffPath, contextDiffPath }: the comparison summary plus two written artifacts, the diff image and a context diff.
It excludes the fixed status-bar band from both the pixel and the OCR comparison. That alone is reason to prefer it over anything hand-rolled. The clock, battery percentage and signal indicators differ between any two captures taken seconds apart, and on a full-screen mobile capture they are the single most common source of false positives. With this tool you neither crop them out nor explain them away in review.
Load the schema before your first call — deferred tools in this environment expose only their name until you load them. ToolSearch for screenshot-diff, read the parameter names and return shape off the schema, and use those. The table above was transcribed from a session with the argent server connected rather than verified in the session that wrote this skill, so treat the loaded schema as the authority wherever the two disagree.
Never invent a tool signature. If ToolSearch cannot load it, the tool is not available to your session — take the § 5 fallback and say so in your report.
From apps/mobile/CLAUDE.md:
bun add -g @swmansion/argent # one-time per machine; puts `argent` on PATHargent-react-native-app-workflow.xcrun simctl install/launch/terminate with argent calls — sim state corruption is real, and a corrupted sim silently poisons a diff.argent init — skills and config are already committed. argent update only when intentionally refreshing.adb -s <serial> reverse tcp:8081 tcp:8081 before the app starts, or Metro is unreachable and you will capture a red screen.stop-all-simulator-servers.Get a udid per argent-ios-simulator-setup (iOS, UUID-shaped) or argent-android-emulator-setup (Android, adb serial). Tool names are identical on both platforms.
A headless or CI agent session cannot drive a local simulator or emulator. There is no sim to attach to, no Xcode/xctrace, no emulator, and no display. This was the blocking finding on INFRA-3096 and it has not changed.
Everything below is the argent-driven local-device path, and that path needs a locally attached simulator, emulator, or device in a session running on a developer machine: argent's MCP server drives a local sim/emulator or an attached device, and a headless session has no argent binary, no argent MCP server, and no device.
If you are headless, do not stage a diff and imply it ran. tamagui-conversion is explicit: a staged-but-unrun diff is not evidence. Instead do one of:
scale: 1.0, the screenshot-diff call and its outputDir, and the state matrix — into the Linear issue and PR body so a human-run session executes it verbatim.tamagui-conversion rule).Those blockers are about driving argent. They are not a claim that on-device visual evidence is impossible without a local simulator. AWS Device Farm runs real remote devices and needs no local sim and no macOS host, so it is a plausible route to the underlying evidence goal — before/after captures from a real device, taken to a pixel diff. What that route carries:
__DEV__-only (§ 8) — a bundled production build cannot show the staged stories at all.The INFRA-3096 "declined" note is narrower than it reads. A Device Farm pass was declined there — but on a batch with zero converted files, where the objection was to diffing a batch in which nothing had changed ("staged evidence with extra steps"), not to Device Farm as a capture mechanism. That is a reading of INFRA-3096 comment fe4db9f3; check it yourself before citing it either way. Whichever capture route you use, "executed, never staged" is unchanged — the diff must actually run, against changed code.
The repo practice this skill formalizes: put both sides in one Storybook file rendering the identical state matrix at identical screen positions, so a diff of the two stories measures nothing but the component.
Pattern files:
apps/mobile/src/components/mycelium/SegmentedControlMigration.stories.tsx (#37202) — the template. Legacy and Rebuilt stories render the exact same matrix at the exact same positions; SideBySide stacks both per state for eyeballing.apps/mobile/src/components/mycelium/ShimmerMigration.stories.tsx — carries a hard-won caveat.Rules that fall out of those two files:
| Case | Stage it as | Why |
|---|---|---|
| Static states | Standalone Legacy + Rebuilt stories, identical positions | Diffable pair, nothing else varies |
| Animated states | SideBySide, both mounts simultaneous | Separate mounts start their animation clocks at different times — an out-of-phase row looks frozen or broken when both implementations are correct. Phase-lock or do not diff. |
| No legacy twin | Not diffable at all | Behavioral tests + requester sign-off instead |
Identical positions is not a nicety. Any offset makes the whole panel differ and the diff is worthless.
Get a udid from list-devices, stage both sides per § 4, and settle per § 6. Then:
Legacy story. Settle, then screenshot with scale: 1.0, persisted to disk.Rebuilt — same route, same scroll offset, no device rotation between captures.screenshot with scale: 1.0, persisted to disk.screenshot-diff with udid, an outputDir, and the two files as baselinePath / currentPath.summary, then open diffPath and contextDiffPath and actually look at them (§ 7).scale: 1.0 is mandatory for anything you feed to a diffscreenshot downscales to 30% of native resolution by default to save context (argent-device-interact § 7). scale accepts 0.01–1.0.
A 30% capture has thrown away the pixels you are measuring: it invents resampling differences that read as regressions, and it hides real one-pixel border and anti-aliasing shifts. A diff run at the default scale is not evidence.
{ "udid": "<UDID>", "scale": 1.0 }If screenshot returns an inline image rather than a file path, you need the bytes on disk before baselinePath / currentPath will accept them. Load the tool's schema with ToolSearch and read its actual return shape — do not assume either form.
captureBaseline / captureCurrent (§ 1) let screenshot-diff take the shots itself instead of you saving PNGs. Read the schema for how the two are sequenced before relying on it: the staged-story pattern in § 4 requires navigating between the two captures, and a before/after across a code change requires a rebuild between them. Prefer the saved-PNG pair whenever anything has to happen in between.
screenshot-diff will not loadFor a session with no argent MCP server connected, or an argent version predating the tool. Capture the pair exactly as above, then diff the PNGs yourself: labs/workbench already carries the engine as devDeps (pixelmatch 7.2.0, pngjs 7.0.0), and labs/workbench/scripts/visual/compare.ts is a working reference for the pixelmatch call, padding, and flag arithmetic — reuse it rather than reaching for a new dependency. You then own the two jobs the first-party tool does for you: masking the status-bar band, and picking thresholds (§ 7).
A flaky diff is worse than no diff: it trains reviewers to ignore the artifact. Fix all of these before capturing.
SideBySide phase-locked mount for anything that moves. Never diff a mid-transition frame.screenshot (the argent-test-ui-flow recovery pattern).screenshot-diff (the primary path)summary is the judgement surface; diffPath and contextDiffPath are the artifacts.
summary, then corroborate it against the images. A summary you did not check against the diff image is a claim, not a verdict.How the harness numbers below translate: they mostly do not. Stated plainly so nobody tries to reconcile them:
| Harness concept | Counterpart in screenshot-diff |
|---|---|
--pixel-threshold (per-pixel color sensitivity) | None exposed. The tool decides its own sensitivity |
--flag-ratio (diffPixels / totalPixels) | None exposed. The tool does not take a flagging rule from you |
--min-diff-pixels (absolute floor) | None exposed |
| Manual status-bar masking | Built in — excluded from pixel and OCR comparison |
| Diff PNG output | diffPath, plus contextDiffPath |
| Flag verdict computed by you | summary, computed by the tool |
Three of the harness's four knobs have no counterpart. That is not a gap to paper over with an invented mapping — it means the judgement moves from choosing numbers to reading summary against a control pair and looking at the diff image.
pixelmatch onlyEverything below applies to the § 5 fallback, not to screenshot-diff.
Established precedent — labs/workbench/scripts/visual/README.md, the web harness. A pair is flagged when both conditions hold:
diffPixels / totalPixels > 0.005 # relative ratio (--flag-ratio)
AND diffPixels > 250 # absolute floor (--min-diff-pixels)with a per-pixel color sensitivity of 0.1 (--pixel-threshold, passed to pixelmatch).
Why two conditions, so you can adapt rather than cargo-cult: the ratio alone over-fires on small panels. On a ~6–10k-pixel panel, sub-perceptual anti-aliasing noise (the harness's documented case: translucent-token compositing rendering text a few 8-bit steps apart) is ~120–180 px — under 250 absolute, but over 0.005 relative. The absolute floor absorbs exactly that. Meanwhile a genuinely broken color repaints most of a panel at high delta and trips both conditions comfortably. The two-condition rule buys you a wide, empty band between noise and real breakage.
Starting recommendations for mobile (mine, not established — tune and record what you used):
pixel-threshold at 0.1. It is the sensitivity that was validated against real token-compositing noise, and it is platform-independent.0.005 ratio.scale: 1.0 are far larger than a workbench panel, and native text rasterization is noisier than Chromium's — 250 px will fire on nothing. Scale the floor with panel area: the web value is ~2.5–4% of a 6–10k-px panel, so start there and calibrate against a no-change control pair (capture the same story twice, unchanged; that number is your noise floor, and your threshold goes above it).Record what you ran alongside the verdict — screenshot-diff and which mode, or the fallback and its thresholds. A verdict without its mechanism is unreproducible.
This diff practice needs Storybook. But apps/mobile/CLAUDE.md mandates NODE_ENV=production for agent/CLI Metro precisely because dev-mode bundles Storybook and the app boots into Storybook UI, "breaking agent-driven interaction flows":
bun nx run mobile:start:production # the normal agent default — Storybook OFFBoth are correct; they are different jobs. Reconcile by choosing the mode for the task, deliberately and out loud:
| Task | Metro mode | Storybook |
|---|---|---|
| Driving real app screens, flows, QA, profiling | mobile:start:production | off (required) |
| Screenshot-diffing a staged story matrix | dev Metro (NODE_ENV unset) | on (required) |
So: a screenshot-diff session runs dev Metro on purpose, and booting into Storybook UI is the desired outcome, not the failure the CLAUDE.md rule warns about. Consequences to hold onto:
NODE_ENV applies at JS-bundle time. For a device build with no Metro dev server the flag goes on the build command, so a bundled production build cannot show Storybook at all — what you need is a dev-mode bundle, and how you get one depends on the route (§ 3): dev Metro against a local simulator, or a dev-mode-bundled build on a remote device.An executed diff is only evidence once it is on the record. Post to both the Linear issue and the PR:
tamagui-conversion) is before/after screenshots plus video for anything animated.screenshot-diff and its mode, or the fallback with the thresholds you used and any deviation from the defaults and why.pass / flagged / known drift (reason). Enumerate the full matrix so a reviewer can see nothing was quietly skipped.Say plainly whether the diff ran. "Staged" and "executed" are different claims and reviewers rely on the difference.
| Skill | When to use |
|---|---|
argent-device-interact | The screenshot tool itself, scale, rotation, gestures, troubleshooting (§ 7 is the screenshot reference) |
argent-test-ui-flow | Navigating to the staged stories; the settle/retake recovery pattern before capture |
argent-ios-simulator-setup | Booting an iOS simulator, getting a UDID |
argent-android-emulator-setup | Booting an Android emulator, getting an adb serial, adb reverse |
argent-create-flow | Record the navigate-and-capture sequence as a flow so before/after captures are byte-for-byte reproducible via flow-execute — this removes interaction variance, the largest source of diff noise |
argent-react-native-app-workflow | Getting the build installed and Metro running before any capture |
tamagui-conversion | The migration contract that requires an executed diff as the mobile proof artifact |
Uniswap/interface
f29d477
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.