Repository navigation
Add --cache-right: replay the reference's network on repeat runs - #21
Merged
Merged
Conversation
The right page still loads, settles and is shot live, so element attribution, blank/failed-asset refusals and unstable detection are unchanged; only round trips to the reference are skipped. Opt-in, a recording is replayed for an hour and only when it covers every shot in the run. The run prints a right: line and records meta.rightCache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shot measured against a replay is marked rightReplayedFrom and reused by --rerun-failed only in a run that would replay it; meta carries the oldest. Recordings drop error responses and repeats, skip shots with failed assets, replace older recordings, keep their index in a sidecar, and a failed save no longer loses the report. .cache survives the wipe only with --cache-right. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 24, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
--cache-right, an opt-in flag that makes repeat runs against the same reference faster. It replays the right side's network traffic from a HAR that an earlier run recorded, so the run doesn't fetch from the reference again.Why HAR replay, not a cached screenshot
locate()needs the right page live, holding the job's document. It probes the right page by pixel box (withelementFromPointocclusion and visibility checks), and it runsdocument.querySelectorwith selectors the left page generated. You can't answer those from a snapshot without a DOM, so caching theShotwould mean dropping or degradingdomattribution. Two more problems with caching shots:--rerun-failedalready reuses shots that came out clean. The shots a loop actually reruns are the ones that differ, which are exactly the ones that need attribution.With replay, the right page still loads, settles and gets shot under this run's clock. Attribution, the HTTP ≥ 400 refusal, blank detection, failed stylesheets and fonts, unstable pixels and the text diff all come from a live render, as before. Only the round trips to the reference are skipped. Left and right capture at the same time, so the reference's network latency is what sets the wall time. That latency is what replay removes.
Cache key and invalidation
<out>/.cache/right-<hash>.har: renderdiff version, browser name and version, right origin, the right storage state's contents,CONTEXT_OPTIONSandblockUrlPatterns. These are what change what the reference serves. Masks, CSS and selectors change only rendering, which still happens live, so they aren't in the key.[route.right, width, before-hook source]. A recording is replayed only when it covers every shot in the run. Otherwise the whole right side loads live and is recorded again.REPLAY_TTL). ETag and Last-Modified don't help here: SSR HTML rarely has them, and they wouldn't catch asset changes. A request the recording doesn't have goes to the network (notFound: "fallback"). Responses with status ≥ 400 are dropped from the recording and entries are deduped to one per method+URL, so a 500 or a once-failed asset is never replayed; shots that errored or had failed assets aren't marked as covered. A failed save only warns (recording not saved); results and the report are still written. Recording metadata lives in a small<har>.jsonsidecar written last, so a half-written save is never replayed.right: replayed from a recording of <ISO> (--cache-right), and Each result measured against a replay recordsrightReplayedFrom, andresults.jsongetsmeta.rightReplayedFrom(the oldest replay among all results, reused ones included; null otherwise).--rerun-failedreuses a replayed result only when this run also passes--cache-rightand the recording is still within the TTL; otherwise that shot reruns live. The report header shows the same thing. SKILL.md tells agents to drop the flag for the final run..cacheis kept only with--cache-right, and saving a recording deletes olderright-*files (they can hold a signed-in session's cookies).--rerun-failed --cache-rightre-record covers only the rerun shots.Measured
Fixture site, 6 routes × 2 widths, with the right origin behind a proxy that adds a fixed delay to every request:
--concurrency 2On the replayed runs the proxy received 0 requests, and the results were identical to the live runs. The run that records costs about the same as a live run.
Tests
tests/unit/cache.test.ts: how recordings merge (errors and duplicates dropped, older recordings replaced), the TTL and coverage rules, and the key (includingblockUrlPatterns).tests/visual/accepted.test.ts: a repeat run loads only the left side and gives the same results, withdomelements still named. A shot the recording lacks makes the run load live and record again. A right route that 500s once isn't replayed as an error.--rerun-failedwithout the flag after a replayed run reruns those shots live.typecheck,lint,format:check,npm test(226) andnpm run test:visual(304) all pass.🤖 Generated with Claude Code