Skip to content

Add renderdiff probe: what varies between loads of one URL - #89

Merged
tmashara merged 3 commits into
mainfrom
claude/issue-73-probe
Oct 29, 2026
Merged

tmashara merged 3 commits into
mainfrom
claude/issue-73-probe

Conversation

@tmashara

@tmashara tmashara commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

What

renderdiff probe <url> [--width <n>] [--loads <n>]: loads one URL several times and lists the elements whose computed style or position varies between loads.

Why

A base-vs-base run can differ with no varies line and 0 unstable pixels: the page is still on every load, but two loads disagree (an embed that sometimes gets its controls bar, a font that sometimes wins the race). Until now the only way to find what to mask was to rerun and squint. Probe names the element and its values.

Usage and output

$ renderdiff probe http://localhost:4000/bistable --width 1440
Probe: http://localhost:4000/bistable @1440, 8 loads
3 varying:
  body > div.page > div.player height 366px ×4, 380px ×4
  body > div.page > section.band y 566 ×4, 580 ×4
  body > div.page > section.notes y 1078 ×4, 1092 ×4

$ renderdiff probe http://localhost:4000/
Probe: http://localhost:4000/ @1440, 8 loads
stable over 8 loads (17 elements)
  • Each load runs a run's left-side pipeline: fresh context, prepare (blocking, fake clock, seeded Math.random, video freeze), shoot/capture (settle, before and beforeShot hooks, masks, retry), then the --computed walk. Positions are read afterwards as x/y relative to the parent element.
  • Loads run in parallel up to --concurrency (a probe worker keeps one page busy, a run keeps two); --concurrency 1 runs them one at a time.
  • Diffs every PROPS field plus content, x, y; a key some loads lack prints once as present, outermost only. The existing collapseInherited drops what a parent's change explains (now generic over the number of values, type-only change; --computed unchanged). First 20 lines, then (+N more).
  • Config masks, --mask, --mask-media, hooks, --storage-state/--left-storage-state, --no-freeze-video and --config apply as in a run.

Exit codes

  • 0: nothing varies
  • 1: something varies
  • 2: bad arguments or config, or the origin is down (no URL, two URLs or routes, --loads under 2 or not an integer, more than one --width, --selector)
  • 3: a load errored (blank page, timeout, hook error) or the probe crashed. No partial result, because partial data would read as stable.

Limits

  • Cold cache on every load (a fresh context each time, like the run's recheck). Variation that only shows on a warm load won't reproduce. Upgrade path: one context per worker with a discarded warm-up load.
  • y also lists the siblings a grown box pushes down (see the example). Document order puts the cause first. Marked ponytail: in src/probe.ts.
  • One width (the first default without --width), no --selector, no --json, no PNGs or out dir, no markup diffs. Can be added if needed.
  • Loads count as the left side: hooks get side: "left".

How tested

  • New generated fixture tests/fixtures/pages/bistable (a box 366px tall on odd renders and 380px on even ones), added to GUARDRAILS, GENERATED and the fixtures README.
  • Visual (tests/visual/guardrails.test.ts): probe /bistable --loads 4 exits 1 and names div.player height with both values, and no div.page height. Probe / --loads 3 exits 0 with stable over 3 loads.
  • Unit (tests/unit/cli-args.test.ts): no URL, two URLs, a route, --loads 1, --loads=2.5, --loads with no value, two --widths and --selector all exit 2 with the reason, and a normal run with --loads is rejected as an unknown flag. The docs-flags test covers --loads across help, README and skills.
  • Without the change, the new unit tests fail (unknown flag: --loads, right origin is required) and so do the visual ones. With it they pass.
  • npm run typecheck, npm run lint, npm run format:check, npm test (326 pass) and npm run test:visual (345 pass) all clean on macOS, rebased on Masks: re-read boxes at every shot, clip to overflow, cover overflowing children #86.

Docs: a README paragraph after renderdiff report, one clause in the skill's baseline step, and a ## probe section in skills/renderdiff/reference.md.

Fixes #73

🤖 Generated with Claude Code

@tmashara
tmashara force-pushed the claude/issue-73-probe branch from b164a14 to 05f5df9 Compare October 29, 2026 18:51
tmashara and others added 2 commits October 29, 2026 19:53
Loads one URL --loads times (default 8) in fresh contexts through a run's
own capture, then diffs every element's computed style and position in its
parent across the loads, collapsing what a parent's change explains. Exits
0 stable, 1 varies, 2 bad arguments, 3 a load errored.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@tmashara
tmashara force-pushed the claude/issue-73-probe branch from 05f5df9 to b07bd0d Compare October 29, 2026 18:56
# Conflicts:
#	tests/fixtures/pages/README.md
@tmashara
tmashara merged commit 56736e9 into main Oct 29, 2026
3 checks passed
@tmashara
tmashara deleted the claude/issue-73-probe branch October 29, 2026 19:03
@tmashara tmashara mentioned this pull request Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

renderdiff probe: list elements whose box varies between loads

1 participant