Skip to content

refactor: extract viewport preparation seam (Capture::Viewport) - #215

Merged
pftg merged 3 commits into
masterfrom
refactor/v2-viewport-seam
Aug 22, 2026
Merged

refactor: extract viewport preparation seam (Capture::Viewport)#215
pftg merged 3 commits into
masterfrom
refactor/v2-viewport-seam

Conversation

@pftg

@pftg pftg commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

What

v2 sequence, 5.5-lite item 6 (per the panel amendment): extract the per-capture viewport preparation into a dedicated seam.

  • New SnapDiff::Capture::Viewport.prepare!(expected_window_size, anchor: nil) — a module function (KISS, no new class hierarchy). It carries the raise-only window-size guard that lived inline as ScreenshotMatcher#check_window_size!.
  • ScreenshotMatcher#build_screenshot_assertion and #capture now call Capture::Viewport.prepare!(Capybara::Screenshot.window_size, anchor: nil) at the same point in the flow where check_window_size! ran.
  • anchor: is accepted and threaded but unused today — it is the v3 seam for scroll-position preservation / element-anchored capture (window resize loses the scroll position of the element under comparison). No scrolling is implemented here.

Behavior equivalence (the hoist check)

The spec required stopping if hoisting the prepare outside the stability retry loop changed observable call counts. Verified by reading and by a pinning guard: the check already ran once per capture, in ScreenshotMatcher, before the screenshoter — never inside StableScreenshoter's retry loop. Nothing in Screenshoter/StableScreenshoter touches window size (the adapters' setup-time resize_window_if_needed is untouched, out of scope). So the extraction is behavior-equivalent by construction: same operation, same order, same cadence, same error message.

Gate evidence

Guard commit (d80897d) pins the cadence first:

  • #capture raises WindowSizeMismatchError on wrong window size (was only covered on the assertion path)
  • window size is checked exactly once per capture even when stability retries happen

Mutation check (run twice — against the inline guard and against the final seam shape):

  • comment out the Capture::Viewport.prepare! calls → screenshot_matcher_test.rb: 3 failures
  • restore → 0 failures

Tests:

  • rake test:unit: 444 runs, 1277 assertions, 0 failures, 0 errors (439 baseline + 2 cadence guards + 3 direct Viewport tests)
  • rake test: 477 runs, 0 failures, 6 skips (the 2 screenshot-report comparison entries are pre-existing on master 99c71a6, verified by running the baseline)
  • standardrb: 138 files, no offenses

Not built (deliberately)

🤖 Generated with Claude Code

Summary by Sourcery

Extract per-capture viewport preparation from ScreenshotMatcher while preserving window-size validation behavior and establishing a seam for future anchored capture support.

New Features:

  • Introduce a dedicated per-capture viewport preparation seam with a reserved anchor parameter for future capture positioning support.

Bug Fixes:

  • Ensure window-size mismatches also prevent the compare-free capture path from writing screenshots.

Enhancements:

  • Move the existing raise-only window-size validation out of ScreenshotMatcher while preserving its timing and behavior.

Tests:

  • Add direct coverage for viewport preparation and verify window-size validation occurs once per capture, including during stability retries.

pftg added 2 commits August 22, 2026 21:25
Guards for 5.5-lite item 6: the window-size guard also fires on the
compare-free #capture path, and the check runs exactly once per capture
(never per stability retry) - the cadence the seam must preserve.

Claude-Session: https://claude.ai/code/session_014BQJX6eWzBj2UTm5zQsjEs
5.5-lite item 6: the raise-only window-size guard moves from
ScreenshotMatcher#check_window_size! into the module function
SnapDiff::Capture::Viewport.prepare! - the per-capture viewport seam.
No new class hierarchy (KISS): a module function, called once per
capture, outside the stability retry loop (which is where the check
already ran - the extraction is behavior-equivalent by construction).

The anchor: nil parameter is accepted and threaded but unused today;
it is the v3 seam for scroll-position preservation and element-anchored
capture (window resize loses the scroll position of the element under
comparison). No scrolling is implemented here.

Claude-Session: https://claude.ai/code/session_014BQJX6eWzBj2UTm5zQsjEs
@sourcery-ai

sourcery-ai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Extracts a dedicated SnapDiff::Capture::Viewport seam for per-capture viewport preparation, moving the window-size guard out of ScreenshotMatcher and pinning its cadence and behavior with focused tests.

Sequence diagram for per-capture viewport preparation

sequenceDiagram
    participant ScreenshotMatcher
    participant Viewport as Capture::Viewport
    participant BrowserHelpers
    participant Screenshoter

    ScreenshotMatcher->>Viewport: prepare!(window_size, anchor: nil)
    Viewport->>BrowserHelpers: window_size_is_wrong?(expected_window_size)
    alt window size mismatch
        Viewport-->>ScreenshotMatcher: raise WindowSizeMismatchError
    else window size valid
        Viewport-->>ScreenshotMatcher: return
        ScreenshotMatcher->>Screenshoter: capture screenshot
    end
Loading

File-Level Changes

Change Details Files
Introduce Capture::Viewport.prepare! as a module-level seam for per-capture viewport preparation and window-size validation.
  • Add SnapDiff::Capture::Viewport module with a module_function prepare! that checks BrowserHelpers.window_size_is_wrong? against an expected window size.
  • On mismatch, compute current window size when using Selenium and raise CapybaraScreenshotDiff::WindowSizeMismatchError with the same error text as the previous inline guard.
  • Accept an anchor keyword parameter in prepare! as a reserved v3 seam for scroll-position preservation without acting on it today.
lib/snap_diff/capture/viewport.rb
Refactor ScreenshotMatcher to delegate window-size checking to Capture::Viewport.prepare! and remove the inline guard method.
  • Require the new capture/viewport file in screenshot_matcher.
  • In build_screenshot_assertion, call Capture::Viewport.prepare!(Capybara::Screenshot.window_size, anchor: nil) before preparing screenshot options, mirroring the previous check_window_size! location.
  • In capture, call Capture::Viewport.prepare!(Capybara::Screenshot.window_size, anchor: nil) before preparing screenshot options, aligning behavior between compare and compare-free paths.
  • Remove the check_window_size! instance method and its inline implementation from ScreenshotMatcher.
lib/snap_diff/screenshot_matcher.rb
Add focused tests to cover the new viewport seam and to pin the window-size guard behavior and cadence across capture paths and stability retries.
  • Add ViewportTest to exercise prepare! as a no-op when BrowserHelpers.window_size_is_wrong? is false, raising WindowSizeMismatchError when true, and accepting the anchor parameter without side effects.
  • Extend ScreenshotMatcher tests to assert that #capture raises WindowSizeMismatchError when window size is wrong on the compare-free path.
  • Add a cadence test ensuring window_size_is_wrong? is called exactly once per capture even when StableScreenshoter performs multiple stability retries by stubbing BrowserHelpers and StableScreenshoter.new.
test/unit/capture/viewport_test.rb
test/unit/screenshot_matcher_test.rb

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@pftg, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 seconds

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 37e1292e-2510-4d2d-8baf-d3a85d745233

📥 Commits

Reviewing files that changed from the base of the PR and between 99c71a6 and 2ce684b.

📒 Files selected for processing (4)
  • lib/snap_diff/capture/viewport.rb
  • lib/snap_diff/screenshot_matcher.rb
  • test/unit/capture/viewport_test.rb
  • test/unit/screenshot_matcher_test.rb

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@pftg
pftg merged commit 633561e into master Aug 22, 2026
6 checks passed
@pftg
pftg deleted the refactor/v2-viewport-seam branch August 22, 2026 19:46
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.

1 participant