Skip to content

feat: deprecate chunky_png, shift_distance_limit and the driver abstraction (removed in 2.1) - #246

Merged
pftg merged 1 commit into
masterfrom
feat/v20-driver-deprecations
Aug 23, 2026
Merged

feat: deprecate chunky_png, shift_distance_limit and the driver abstraction (removed in 2.1)#246
pftg merged 1 commit into
masterfrom
feat/v20-driver-deprecations

Conversation

@pftg

@pftg pftg commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

2.0 is the transitional release: the contract is published before it is enforced. The
legacy-namespace half of that already warns. The driver half — everything 2.1 removes so
that libvips becomes the only backend — warned about nothing. This adds it.

What now warns (once per process, per subject)

Fires when Where it fires from
the chunky_png driver is selecteddriver: :chunky_png, SnapDiff.config.driver =, the legacy Diff.driver = Utils.find_driver_class_for — the one funnel every selection surface ends up in
driver: :auto falls back to chunky_png because libvips is absent same funnel, distinct message and key: these users never asked for chunky and have no idea they are on it
shift_distance_limit is set — globally or per comparison Config#shift_distance_limit= and Comparison#initialize (non-nil only), one shared dedup key
SnapDiff::Drivers.loaded is read — the documented custom-driver registration point Drivers.loaded
SnapDiff::Drivers.available is read Drivers.available
a custom driver does include SnapDiff::Driver Driver.included, scoped off the gem's own two drivers

Not warned on, deliberately: Drivers.for (the gem calls it for every comparison — warning
there would fire on vips-only setups that nothing in this list affects), driver detection
(Drivers.detect_available / Utils.detect_available_drivers, which run at load before any
user code), and the eager legacy constant aliases LOADED_DRIVERS / AVAILABLE_DRIVERS
(plain constants, nothing to hook). All three are documented as silent in docs/drivers.md
and docs/UPGRADING.md, with the warning-capable name to use instead.

Machinery

SnapDiff::Removal (lib/snap_diff/removal.rb) — same shape, same channel (Kernel#warn),
same silencing switches (SnapDiff.silence_deprecations / SNAP_DIFF_SILENCE_DEPRECATIONS)
as SnapDiff::Deprecation, but its own file: the legacy shims and their deprecation channel
are part of what gets deleted, while these call sites (utils, config, comparison, drivers) are
core files that outlive them and cannot depend on a doomed file. SnapDiff.silence_deprecations
moved there for the same reason — it is the one switch that silences both halves.

Two internal seams so the gem never warns at itself: Drivers.registry (the unannounced
registry the gem reads) and reading Drivers::AVAILABLE_DRIVERS directly instead of
.available.

Evidence

test/unit/removed_in_2_1_deprecation_test.rb — 13 subprocess probes ("once per process" cannot
be measured inside one long-lived suite). Mutation-tested: dropping the dedup → 9 red; dropping
the silence check → 2 red; routing an internal read back through .loaded → 5 red; unscoping the
mixin hook → 5 red; warning on the presence of shift_distance_limit rather than a real value →
4 red.

  • rake test:unit 585 runs / 0 failures (baseline 572)
  • rake test:canonical 482 runs / 0 failures / 1 skip
  • standardrb clean

Docs

docs/drivers.md (what goes, what replaces it, libvips becomes required, and that custom
drivers have no migration path), docs/UPGRADING.md (third warning class next to the two
namespace ones), docs/configuration.md (shift_distance_limit), docs/snapdiff.md (the
custom-driver page a driver author actually lands on).

Summary by Sourcery

Warn users about driver-related features removed in 2.1 and guide them toward libvips before the transition.

Enhancements:

  • Add once-per-process removal warnings for the ChunkyPNG backend, automatic ChunkyPNG fallback, shift_distance_limit, driver registries, and custom driver mixins ahead of the 2.1 removal.
  • Centralize removal-warning behavior with shared silencing controls and prevent internal driver lookups from generating user-facing warnings.

Documentation:

  • Document the 2.1 transition to libvips as the sole backend, including migration guidance, silent APIs, and the lack of a custom-driver replacement.

Tests:

  • Add subprocess coverage for warning subjects, deduplication, silence controls, and warning-free internal or VIPS-only usage.

…action (removed in 2.1)

2.0 is the transitional release: the contract is published before it is
enforced. The legacy-namespace half of that promise already warns. The
driver half -- everything 2.1 removes so that libvips becomes the only
backend -- warned about nothing. A user on `driver: :auto` without libvips
had no way to learn that 2.1 stops comparing for them.

Six warnings, each once per process, each silenceable through the existing
switches (`SnapDiff.silence_deprecations`, SNAP_DIFF_SILENCE_DEPRECATIONS):

- chunky_png SELECTED, from Utils.find_driver_class_for -- the one funnel
  every selection surface ends up in (`driver:` per comparison,
  SnapDiff.config.driver, the legacy Diff.driver=).
- `:auto` FALLING BACK to chunky_png because libvips is absent. Same funnel,
  its own message and key: these users never asked for chunky_png.
- shift_distance_limit SET, from Config#shift_distance_limit= and from
  Comparison#initialize (non-nil only -- default_options carries the key on
  every comparison). One shared key, so it warns once whichever fires.
- Drivers.loaded, the documented custom-driver registration point.
- Drivers.available, i.e. driver detection.
- `include SnapDiff::Driver` by a class that is not one of the gem's own.

NOT warned on, and documented as silent: Drivers.for (the gem calls it for
every comparison, so warning there would fire on vips-only setups that
nothing here affects), detection itself (runs at load, before user code),
and the eager LOADED_DRIVERS / AVAILABLE_DRIVERS constant aliases (nothing
to hook). Each entry names the warning-capable equivalent instead.

The machinery is SnapDiff::Removal -- same channel, same discipline and the
same silencing switches as SnapDiff::Deprecation, in its own file: the
legacy shims and their deprecation channel are part of what the deletion
removes, while these call sites (utils, config, comparison, drivers) are
core files that outlive them and cannot depend on a doomed file.
SnapDiff.silence_deprecations moved there for the same reason -- it is the
one switch that silences both halves -- which is why the canonical-suite
gate no longer lists it as shim-only surface.

Two internal seams keep the gem from warning at itself: Drivers.registry
(the unannounced registry the gem reads) and reading the
AVAILABLE_DRIVERS constant directly instead of .available. test_helper
suppresses the channel the same way it already suppresses the migration
notice: this suite runs its whole matrix on chunky_png by design.

Evidence: 13 subprocess probes ("once per process" cannot be measured
inside one long-lived suite), mutation-tested -- dropping the dedup goes 9
red, dropping the silence check 2 red, routing an internal read back
through .loaded 5 red, unscoping the mixin hook 5 red, warning on the
presence of shift_distance_limit rather than a value 4 red.

rake test:unit 585/0, rake test 613/0/1, rake test:canonical 482/0/1,
standardrb clean.

@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.

Sorry @pftg, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Aug 23, 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: 44 minutes

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: c282b531-ad56-41dc-9808-47e9a05a9c6c

📥 Commits

Reviewing files that changed from the base of the PR and between ebe88a2 and 54187eb.

📒 Files selected for processing (16)
  • docs/UPGRADING.md
  • docs/configuration.md
  • docs/drivers.md
  • docs/snapdiff.md
  • lib/snap_diff/comparison.rb
  • lib/snap_diff/config.rb
  • lib/snap_diff/deprecation.rb
  • lib/snap_diff/driver.rb
  • lib/snap_diff/drivers.rb
  • lib/snap_diff/legacy_shims.rb
  • lib/snap_diff/removal.rb
  • lib/snap_diff/utils.rb
  • test/legacy/legacy_namespace_deprecation_test.rb
  • test/test_helper.rb
  • test/unit/canonical_suite_has_no_legacy_refs_test.rb
  • test/unit/removed_in_2_1_deprecation_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 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduces a dedicated SnapDiff::Removal warning channel for features that will be removed in 2.1 (chunky_png driver, shift_distance_limit, and the driver abstraction) and wires it into core code paths, while updating docs and tests to reflect and verify the new removal warnings and shared silencing behavior.

Sequence diagram for driver removal warnings

sequenceDiagram
    participant User
    participant Utils
    participant Removal
    participant Drivers
    participant Comparison

    User->>Comparison: initialize(image_path, base_image_path, options)
    Comparison->>Removal: warn_once(:shift_distance_limit, ...)
    Comparison->>Drivers: for(driver_options)
    Drivers->>Utils: find_driver_class_for(driver)
    alt driver == :chunky_png
        Utils->>Removal: warn_once(:chunky_png, ...)
    else driver == :auto and chunky_png selected
        Utils->>Removal: warn_once(:chunky_png_auto, ...)
    end
    Utils->>Drivers: registry[driver]
    Removal-->>User: Kernel.warn once per subject
Loading

File-Level Changes

Change Details Files
Add SnapDiff::Removal module as the dedicated removal-warning channel and move silence_deprecations there so both namespace and driver removals share the same switch.
  • Create lib/snap_diff/removal.rb implementing warn-once-per-subject-per-process removal warnings with origin reporting and a per-process suppress! helper.
  • Expose SnapDiff.silence_deprecations and silence_deprecations? in SnapDiff::Removal, including SNAP_DIFF_SILENCE_DEPRECATIONS env handling, and remove this logic from SnapDiff::Deprecation.
  • Use a shared GEM_LIB_DIR and origin_for logic to attribute warnings to user code rather than internal frames.
lib/snap_diff/removal.rb
lib/snap_diff/deprecation.rb
Wire removal warnings into driver selection, driver registry APIs, and the custom driver mixin so all driver-related features removed in 2.1 now emit once-per-process warnings.
  • Update SnapDiff::Utils.find_driver_class_for to warn when :chunky_png is selected directly or via :auto fallback, using distinct messages and direct access to Drivers::AVAILABLE_DRIVERS and Drivers.registry.
  • Add SnapDiff::Drivers.registry as the internal registry and refactor SnapDiff::Drivers.loaded to warn about 2.1 removal before returning the registry.
  • Add a warning to SnapDiff::Drivers.available about the removal of detection in 2.1 while still returning AVAILABLE_DRIVERS.
  • Implement SnapDiff::Driver.included hook that warns when non-SnapDiff classes include the driver mixin, ensuring bundled drivers do not trigger warnings.
  • Adjust error messaging and legacy_shims to use Drivers.registry/AVAILABLE_DRIVERS directly to keep internal usage silent.
lib/snap_diff/utils.rb
lib/snap_diff/drivers.rb
lib/snap_diff/driver.rb
lib/snap_diff/legacy_shims.rb
Introduce removal warnings for shift_distance_limit, both at configuration time and per-comparison option use.
  • Require snap_diff/removal in Config and Comparison so they can trigger removal warnings without depending on the legacy deprecation file.
  • Exclude shift_distance_limit from generated attr_accessor and implement a custom writer that warns (once per process) when set to a non-nil value.
  • Add a check in Comparison#initialize that warns via SnapDiff::Removal when options[:shift_distance_limit] is present, accounting for default_options carrying nil values.
lib/snap_diff/config.rb
lib/snap_diff/comparison.rb
Add a dedicated subprocess-based test suite verifying all 2.1 removal warnings fire exactly once, are correctly scoped, and respect silencing, while keeping the main suite silent via suppression.
  • Create test/unit/removed_in_2_1_deprecation_test.rb that runs multiple Ruby subprocess probes to assert warning behavior for chunky_png selection, :auto fallback without vips, shift_distance_limit uses, Drivers.loaded/available, and custom driver inclusion.
  • Ensure tests cover the silent cases (plain vips setups, gem load paths, internal usage) and warning dedup across multiple surfaces.
  • Use SnapDiff::Removal.suppress! in test/test_helper and legacy_namespace_deprecation_test probes to keep the canonical and legacy suites free of removal warnings while asserting them in subprocesses.
test/unit/removed_in_2_1_deprecation_test.rb
test/test_helper.rb
test/legacy/legacy_namespace_deprecation_test.rb
Update documentation to describe the new removal warnings, clarify that libvips becomes the only backend in 2.1, and mark driver-related APIs and shift_distance_limit as removed in 2.1 with guidance for migration or lack thereof.
  • Extend docs/UPGRADING.md with a third class of deprecation warnings for 2.1 driver feature removals, including detailed tables of what triggers warnings and what to do instead.
  • Annotate shift_distance_limit in docs/configuration.md and its section as ChunkyPNG-only and removed in 2.1, recommending alternative configuration knobs.
  • Update docs/drivers.md to document that everything except VIPS is removed in 2.1, list the removed driver features, silent-but-removed names, and emphasize libvips as a hard requirement and lack of custom-driver migration path.
  • Mark SnapDiff::Driver and SnapDiff::Drivers as removed in 2.1 in docs/snapdiff.md and add a prominent note in the Custom drivers section explaining that the abstraction has no 2.1 replacement and where to coordinate if maintaining a driver.
docs/UPGRADING.md
docs/configuration.md
docs/drivers.md
docs/snapdiff.md
Tighten the canonical suite’s legacy-reference guard to reflect the relocation of silence_deprecations and avoid flagging the new removal machinery as legacy.
  • Update the LEGACY_SHIM_SURFACE regex in canonical_suite_has_no_legacy_refs_test to remove SnapDiff.silence_deprecations from the legacy surface list, since it now lives in snap_diff/removal.rb as a shared switch for both deprecation channels.
  • Document in comments that the silence switch is intentionally excluded because it’s part of the core removal machinery, not the v1 shims slated for deletion.
test/unit/canonical_suite_has_no_legacy_refs_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

@pftg
pftg merged commit 08b3343 into master Aug 23, 2026
6 checks passed
@pftg
pftg deleted the feat/v20-driver-deprecations branch August 23, 2026 17:17
pftg added a commit that referenced this pull request Aug 24, 2026
beta3 fixed the canonical entry points and shipped almost none of the behaviour.
beta4 is the prerelease the 2.0.0 entry actually describes: the four green-suite
bugs (#254 #255 #256 #258), the accept workflow (#259), the legible failure
message (#264), and the deprecation warnings that make 2.1's removals visible
(#246 #263).

- `lib/snap_diff/version.rb` -> 2.0.0.beta4. Nothing else holds a version; the
  gemspec, the legacy version file and the mirror gemspec all read it. Verified
  with the release workflow's own guard command:
  `ruby -I lib -r capybara/screenshot/diff/version -e "puts Capybara::Screenshot::Diff::VERSION"`
  => 2.0.0.beta4
- CHANGELOG: a `[v2.0.0.beta4]` section written as the delta from beta3, plus the
  record-modes PLACEHOLDER filled from #259 now that it has shipped.
  `grep -n PLACEHOLDER CHANGELOG.md` returns nothing.
- Install snippets moved beta3 -> beta4 in README, docs/UPGRADING.md and
  docs/migration-guide.md. They stay PINNED: `~> 2.0` resolves to nothing while
  only prereleases exist, so unpinning belongs to 2.0.0 final, not here.

The notes name the #272 caveat explicitly. Removing `skip_area`'s implicit
stabilization wait (10.012 s -> 0.009 s measured) means a selector not yet in
the DOM now yields no mask, silently, where it previously resolved after the
wait. #277's run-level tally shipped in the same beta as the replacement signal,
and the notes say so rather than leaving it to be discovered.

Gates: `rake test:unit` 720 runs / 2124 assertions / 0 failures / 0 skips under
CI=true on 4.0.6, `standardrb lib test` clean over 161 files, and
`gem build` produces capybara-screenshot-diff-2.0.0.beta4.gem (93 files, 13 docs,
RELEASE_PREP correctly excluded).
@nevans

nevans commented Aug 24, 2026

Copy link
Copy Markdown

Big up-front caveat: I'm an outsider looking in to this project. I'm really only in the very early stage of evaluating and using it in my own projects. I'm only currently using it indirectly for comparing svg -> png images that my app generates, not even for capybara screenshots! This decision doesn't (currently) affect me! With that out of the way:

I'm curious about why the driver abstraction is being removed? I totally understand the desire to simplify, multiple drivers can't really be expected to behave the exact same or honor all of the same configuration values, and ChunkyPNG isn't recently maintained and probably very rarely used, and it's also reasonable to cut unused code. So, I see some motivating factors, I'm really just curious.

Because having some sort of image comparison driver abstraction seems like a very obvious and useful extension point. Although ChunkyPng is slower today and isn't being actively maintained, there are still risks associated with bringing in libvips1 that some projects may wish to avoid, even just for dev/test dependencies. And with the rapid improvements to YJIT and ZJIT2, or compatibility improvements to alternate runtimes like JRuby or TruffleRuby, it's far less obvious that libvips will be the "winner" a couple of years from now. Also, dropping support for chunky_png doesn't require dropping the driver API entirely. The existence of a variety of image diff tools today suggests that it may be useful to use different comparison tools and/or algorithms, without needing to fork the project or add a bunch of new config options3.

All of that said, it also makes sense that this driver API may not the right API. In that case, it might make sense to drop the current "driver" API and bringing it back a different one in a different form?

And anyway, I'm not maintaining this project nor am I volunteering to help, so... maybe I shouldn't be second guessing this decision. I'm only commenting because I'm actively evaluating snap-diff right now4. Please take my words with a huge grain of salt! 😉

Footnotes

  1. For example, see the recent CVEs which affected rails ActiveStorage through its libvips integration.

  2. See ruby-bench's chunky_png benchmark: YJIT has been sitting at 2.1x-2.2x for a while, and ZJIT recently caught up. Although, to be fair, the chunky_png benchmark is more of a microbenchmark

  3. Most notably, I've looked at odiff as a fast image diff tool. I've also seen a similar diff abstraction layer in other tools, e.g: Visual Regression Tracker's providers.

  4. In addition to the previously mentioned test that compares our generated svg -> png images, I'm moving an entire set of system tests over to it, adding new set of visual baseline fixtures for our ViewComponent previews and Lookbook scenarios, and getting some basic light/dark mode contrast testing in place.

@pftg

pftg commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

@nevans thanks for checking the code. Could you elaborate/clarify, shorter and simpler. I’m afraid that I did not get from the comment what exactly do you want.

Also could you share your use cases. How do you use the gem.

The reason why I want to drop, because nobody used it, at least what I know. But we spend energy to support it. And when we will get a new feature request or confirm demand we will be able to add new driver in no time.

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.

2 participants