snapdrift 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.17.0 - 2026-10-28
6
+
7
+ Workspace releases this version:
8
+
9
+ - `@snapdrift/compare-core` 1.8.0 — policy v2 offset-run alignment suppresses mobile rendering noise: structural padding is skipped, compensating duplicate bands are recognized, unresolved shift-boundary bands split into ink plus padding, localized glyph/badge displacement requires background and foreground evidence, a stable row keeps its own offset unless its own cost exceeds `LOCAL_MISMATCH_COST`, and the offset scoring budget admits a 1170×17187 pair. Ambiguous exact alignment tries the offset mapping before coordinate fallback.
10
+ - `@snapdrift/adapter-fs` 1.7.2 — now depends on `@snapdrift/compare-core ^1.8.0` for the released comparison engine.
11
+
12
+ `@snapdrift/manifest` stays 1.8.0 and `@snapdrift/adapter-report-md` stays 1.4.0. Root floors move to `@snapdrift/compare-core ^1.8.0` and `@snapdrift/adapter-fs ^1.7.2`; the previous `adapter-fs` 1.7.1 and `compare-core` 1.7.0 do not satisfy them, so installing `snapdrift` 0.17.0 always gets the released engine. The dispatcher pins `2d93a54`, the #212 merge commit on `main`, which includes the newest commit touching `actions/` (`1cbf629`, the shell-only Playwright install).
13
+
14
+ ### Fixes
15
+
16
+ - `@snapdrift/compare-core`: policy v2 (`alignment: 'vertical'`) no longer falls back to coordinate comparison with `fallbackReason: 'ambiguous'` just because a page grew by rows that match repeated card padding or background rows elsewhere in the image. An unmatched row now counts as ambiguous only when it occurs fewer than `MIN_STRUCTURAL_ROW_REPEATS` (16, newly exported) times on either side. On a real 1440x3386 page screenshot pair where one card grew by 22 rows, this took the reported drift from 10.83% in coordinate fallback to 1.43% aligned.
17
+ - `@snapdrift/compare-core`: offset-run content intervals now report a row-weighted `meanCost` when adjacent ±1 runs merge, instead of averaging the two run means. This is diagnostic output only; row plans and metrics are unchanged.
18
+ - `@snapdrift/compare-core`: the exact row alignment no longer wraps the Myers search in a bare `catch`. An exhausted edit budget still falls back as `alignment-limit`; an unexpected error now surfaces instead of being reported as that fallback.
19
+ - `@snapdrift/compare-core`: policy v2 no longer throws `comparison_too_large` when the exact Myers row alignment produces an output canvas above `maxPixels`. The exact and offset-run alignment paths now agree: an over-budget aligned canvas falls back to coordinate comparison with `fallbackReason: 'alignment-limit'`, so a pair v1 compares on a fitting union canvas completes under v2 (repro: baseline `[A,X,B,C]` vs current `[A,B,C,Y]` at width 1 and `maxPixels` 4). The coordinate union guard still throws `comparison_too_large`. Closes #199.
20
+ - `@snapdrift/compare-core`: policy v2 vertical alignment no longer paints mobile rendering noise as content. Offset-run plans skip structural padding (a one-sided row needs `CONTENT_INK_MIN`, 16, full-width ink pixels), treat a one-sided content row that duplicates a claimed row within `STRUCTURAL_DUPLICATE_COST` 0.05 at an offset within `STRUCTURAL_DUPLICATE_WINDOW` (3) of the neighboring stable runs as a compensating band, and split an unresolved shift-boundary band into changed-ink rows plus padding instead of deleting or painting the whole band. Inside a paired row, fewer than 16 mismatched pixels are a localized glyph or badge displacement only when every mismatch carries both background evidence (the old baseline pixel exists in a claimed current row) and foreground evidence (the new current pixel exists in a claimed baseline row) at one common non-zero offset within ±3; inserted current rows and deleted baseline rows count as claimed rows on their respective sides. A mismatch with only one side's duplicate, such as a new small mark on uniform background, stays orange as content. A displaced run bordering unexplained mismatched content or content-carrying one-sided rows stays painted as part of that edit. A stable row refines to a ±1 neighbor only when its own recorded-offset cost exceeds `LOCAL_MISMATCH_COST` 0.5; a smaller-residual row keeps its own offset so its nominal current row is compared instead of being consumed without comparison. On the mobile capture pair in Snap visual run `run_6c287da7aef84a94bd663c01` (1170×17187 baseline vs 1170×17268 current), this took the reported mobile diff from `0.18773458736975068` (3,792,907 / 20,203,560 px) in coordinate fallback to `0.0036852305076227753` (74,942 / 20,335,770 px) with `alignmentStrategy: "offset"`. The 74,942 px are the real paragraph reflow (58,557 orange changed + 16,380 green inserted); five residual rendering-noise pixels at output rows 8720..8734 remain, whose mismatches find no claimed duplicate. The desktop diff stayed `0.0019953484937979917` (9,729 / 4,875,840 px), so both routes now sit below the configured 1% threshold.
21
+ - `@snapdrift/compare-core`: an exact-alignment `ambiguous` result now tries the offset-run mapping before falling back to v1 coordinate comparison, so a page that grew by rows matching repeated card padding can align instead of taking a whole-page coordinate diff. Three offset-run boundary cases are fixed: the Viterbi incumbent is free only when its own row cost is zero (a row matching several offsets with ink residue still votes to leave), an unresolved row keeps its best in-window offset as evidence and only a ±1 neighbor refines to the full window when the row's own cost exceeds `LOCAL_MISMATCH_COST` 0.5, and a pair or evidence offset is accepted only within `STRUCTURAL_DUPLICATE_WINDOW` (3) of the neighboring stable runs; a final run overflowing the current height trims to a deletion instead of abandoning the mapping. The offset scoring budget rises to `MAX_OFFSET_SCORE_VISITS` 2.5e9 so a 1170×17187 pair (~2.02e9 visits) scores instead of falling back; the 1440×3386 desktop pair stays ~0.49e9.
22
+
23
+ ### Performance
24
+
25
+ - `@snapdrift/compare-core`: policy v2 metrics-only comparisons (`renderDiffImage: false`) skip the per-pixel loop for byte-identical matched row runs and count them straight into the denominator.
26
+ - `@snapdrift/compare-core`: offset-run analysis caches the pass-1 full-width `hotRuns` scan for each stable row and reuses it when summarizing content intervals, instead of scanning every stable row a second time. A row whose offset changes after gap bridging or sliver absorption is scanned once at its final offset. Interval `highlightRows`/`structuredRows`/`meanCost`, row plans, and metrics are unchanged. Measured on the insertion-shift fixture (1440×5622 baseline, 1440×5644 current) in plain Node, best of 3 alternating runs: `analyzeOffsetRuns` 2518.1 ms -> 2500.3 ms (-0.7%); `compareImages(renderDiffImage: false)` 2924.3 ms -> 2862.7 ms (-2.1%).
27
+ - `@snapdrift/compare-core`: exact row alignment now fingerprints rows with two independent 32-bit FNV-style accumulators over `Uint32Array` word views (byte hashing when the view cannot be built) packed into one combined 53-bit JS safe integer (`high * 2^32 + low`) instead of Node SHA-1 hex digests. A plain 32-bit value over ~16k rows has a few-percent birthday collision chance (~1.6% at 16,384 rows over 2^32); the packed 53-bit value shrinks that to roughly 7.5e-9, and `rowsEqual` still byte-verifies every Myers match, so a collision still falls back as `verification-failed`. No BigInt is used. Best of 3 in plain Node outside Jest: the insertion-shift fixture (1440x5622) fingerprint-only 58.4ms -> 53.2ms (-8.9%) and alignRows 168.2ms -> 150.9ms (-10.3%); a tall synthetic 1440x8000 pair with one inserted row fingerprint-only 64.6ms -> 51.8ms (-19.8%), alignRows 64.7ms -> 51.9ms (-19.7%), and full metrics-only `compareImages` 402.9ms -> 386.3ms (-4.1%). Row mappings and metrics are identical before and after; the fixture's full comparison is dominated by the unchanged offset-run path, so its metrics-only time stayed ~2890ms.
28
+
29
+ ### Features
30
+
31
+ - `@snapdrift/compare-core`: policy v2 aligned comparison metadata now identifies the row-alignment strategy (`alignmentStrategy: "exact" | "offset"`) and records the stable offset-run per-channel tolerance (`tolerance: 32`). Local-mismatch residual bands use a stricter `NOISE_DELTA` floor (2) and are not covered by `tolerance`. `mode` remains `"vertical-aligned"`; a coordinate fallback carries neither field. Additive metadata so consumers can tell byte-identical exact matches from tolerant stable-run matches and know residual bands are stricter. Closes #200.
32
+
33
+ ### Refactors
34
+
35
+ - `@snapdrift/compare-core`: `compareBuffers`, `compareWithIgnoreRegions`, and `generateDiffImage` now delegate to one internal strict decoded-PNG pixel loop instead of keeping three copies of RGBA equality, ignore-mask handling, and diff painting. Public signatures, dimension-mismatch errors, result shapes, byte-equality and re-encode behavior, ignore gray, highlight color, and the hidden-RGB semantics are unchanged. Closes #203.
36
+ - `@snapdrift/compare-core`: `offset-align` exports `rowInkCost`, `rowHasAnyInk`, and `rowHasContentInk` plus the new `CONTENT_INK_MIN`, `STRUCTURAL_DUPLICATE_COST`, and `STRUCTURAL_DUPLICATE_WINDOW` constants so the render plan and painter share one ink and duplicate definition. These are module-level exports; the package index is unchanged.
37
+
5
38
  ## 0.16.1 - 2026-10-27
6
39
 
7
40
  Workspace releases this version:
package/README.md CHANGED
@@ -48,7 +48,7 @@ You keep ownership of checkout, build, startup, readiness, and teardown. SnapDri
48
48
 
49
49
  ```yaml
50
50
  - name: SnapDrift Baseline
51
- uses: ranacseruet/snapdrift@v0.16.1
51
+ uses: ranacseruet/snapdrift@v0.17.0
52
52
  with:
53
53
  mode: baseline
54
54
  repo-config-path: .github/snapdrift.json
@@ -60,7 +60,7 @@ With `provider: "snap"`, baseline publication is a complete, fail-closed snapsho
60
60
 
61
61
  ```yaml
62
62
  - name: SnapDrift Report
63
- uses: ranacseruet/snapdrift@v0.16.1
63
+ uses: ranacseruet/snapdrift@v0.17.0
64
64
  with:
65
65
  mode: pr-diff
66
66
  github-token: ${{ secrets.GITHUB_TOKEN }}
package/action.yml CHANGED
@@ -155,7 +155,7 @@ runs:
155
155
  - name: SnapDrift baseline
156
156
  id: baseline
157
157
  if: inputs.mode == 'baseline'
158
- uses: ranacseruet/snapdrift/actions/baseline@1cbf62965475bc856218ce46eb46dccf7c05326f # v0.16.1
158
+ uses: ranacseruet/snapdrift/actions/baseline@2d93a5499daf6e0273ed11da69b364d33dc8d4d2 # v0.17.0
159
159
  with:
160
160
  repo-config-path: ${{ inputs.repo-config-path }}
161
161
  route-ids: ${{ inputs.route-ids }}
@@ -165,7 +165,7 @@ runs:
165
165
  - name: SnapDrift pull request diff
166
166
  id: pr-diff
167
167
  if: inputs.mode == 'pr-diff'
168
- uses: ranacseruet/snapdrift/actions/pr-diff@1cbf62965475bc856218ce46eb46dccf7c05326f # v0.16.1
168
+ uses: ranacseruet/snapdrift/actions/pr-diff@2d93a5499daf6e0273ed11da69b364d33dc8d4d2 # v0.17.0
169
169
  with:
170
170
  github-token: ${{ inputs.github-token }}
171
171
  repo-config-path: ${{ inputs.repo-config-path }}
package/docs/contracts.md CHANGED
@@ -293,17 +293,24 @@ objects, `dimensionsChanged`, and the effective `totalPixels` denominator. A
293
293
  local changed item with a generated image contains `diffImagePath`, relative to
294
294
  the diff output bundle (for example `diffs/home.png`).
295
295
 
296
- Policy v2 comparison metadata additionally includes `policyVersion: 2` and
297
- `mode`. An aligned result has `mode: "vertical-aligned"` and a compact
298
- `rowMapping` describing matched, changed, inserted, and deleted row runs. A
299
- matched offset-run was mapped and fell below the highlight rule, so its rows
300
- need not be byte-identical. A segment may include `comparedOffset` of `-1` or
296
+ Policy v2 comparison metadata additionally includes `policyVersion: 2`,
297
+ `mode`, and, for aligned results, `alignmentStrategy`. An aligned result has
298
+ `mode: "vertical-aligned"` and a compact `rowMapping` describing matched,
299
+ changed, inserted, and deleted row runs. `alignmentStrategy: "exact"` records
300
+ the Myers fingerprint path: matched rows are byte-identical and `tolerance` is
301
+ absent. `alignmentStrategy: "offset"` records the piecewise vertical-offset
302
+ fallback: `tolerance` is the maximum per-channel delta that stays unhighlighted
303
+ on stable offset-run rows (`STRUCTURED_DELTA`, 32). Local-mismatch residual
304
+ bands between stable runs instead highlight deltas above `NOISE_DELTA` (2), so
305
+ `tolerance` does not cover those rows. A matched stable offset-run row was
306
+ mapped and fell below the 32 rule, so its rows need not be byte-identical. A segment may include `comparedOffset` of `-1` or
301
307
  `1` when the compared current row is one pixel away from `currentStart`; output
302
- row `i` was compared to current row `currentStart + i + comparedOffset`. A `mode: "coordinate-fallback"` result
303
- includes `fallbackReason` when widths, ignore regions, ambiguity, or the
304
- alignment resource limit prevent safe row alignment. The aligned canvas may be
305
- taller than either source image because inserted and deleted rows each occupy
306
- an output row.
308
+ row `i` was compared to current row `currentStart + i + comparedOffset`. A
309
+ `mode: "coordinate-fallback"` result includes `fallbackReason` when widths,
310
+ ignore regions, ambiguity, or the alignment resource limit prevent safe row
311
+ alignment, and carries neither `alignmentStrategy` nor `tolerance`. The aligned
312
+ canvas may be taller than either source image because inserted and deleted rows
313
+ each occupy an output row.
307
314
 
308
315
  When a changed item has a generated local `diffImagePath`, its PR comment cell
309
316
  links to the uploaded report artifact (or the workflow run when no artifact URL
@@ -339,7 +346,7 @@ Additional missing-baseline fields: `baselineAvailable`, `currentResultsPath`.
339
346
  - `diff.threshold` applies per screenshot
340
347
  - Missing captures are counted separately from drift signals
341
348
  - Comparison policy v1 `{ "version": 1, "threshold": number }` is deprecated but remains the compatibility default. Images are top-left aligned on a max-dimension union canvas with no scaling. Overlap pixels are compared, one-sided pixels count as changed (including transparent pixels), and empty union corners do not enter the denominator.
342
- - Comparison policy v2 `{ "version": 2, "threshold": number }` is opt-in. When source widths match and no ignore regions are requested, rows are fingerprinted and aligned with a bounded Myers sequence diff. If that search hits its edit cap, long vertical-offset runs are mapped instead. Inside a run, a row may be compared one pixel above or below the recorded current row when that neighbor matches. `currentStart` still consumes each current row once, so that neighbor is not a separate insertion or deletion. When the compared row differs, the segment includes `comparedOffset` of `-1` or `1`: output row `i` was compared to current row `currentStart + i + comparedOffset`. On that offset-run path, a pixel whose red, green, blue, or alpha channel differs by 32 or less stays unhighlighted, so rendering noise and soft edges remain matched. A short cluster of larger channel deltas is an orange edit. A wide band of those pixels is a local mismatch: it is highlighted in place and does not discard the rest of the page. Unmatched baseline rows are red deletions. Current rows with no baseline partner are green insertions. `matched` on this path means the row was mapped and fell below this highlight rule. Exact fingerprint matches remain byte-identical. Width changes, ignore regions, ambiguous exact-row matches, and resource limits fall back to v1 coordinate comparison and record the fallback reason. This is a raster row alignment aid, not DOM or arbitrary component matching.
349
+ - Comparison policy v2 `{ "version": 2, "threshold": number }` is opt-in. When source widths match and no ignore regions are requested, rows are fingerprinted and aligned with a bounded Myers sequence diff. If that search hits its edit cap, long vertical-offset runs are mapped instead. Inside a run, a row is compared one pixel above or below the recorded current row only when the row's own recorded-offset ink cost exceeds `LOCAL_MISMATCH_COST` (0.5) and a ±1 neighbor matches; a stable row with a smaller residual keeps its own offset, so its nominal current row is still compared instead of being consumed without comparison. `currentStart` still consumes each current row once, so that neighbor is not a separate insertion or deletion. When the compared row differs, the segment includes `comparedOffset` of `-1` or `1`: output row `i` was compared to current row `currentStart + i + comparedOffset`. On stable offset-run rows, a pixel whose red, green, blue, or alpha channel differs by 32 or less stays unhighlighted, so rendering noise and soft edges remain matched. A local-mismatch residual band between stable runs is stricter: a pixel there is highlighted when any channel differs by more than `NOISE_DELTA` (2). A short cluster of such larger channel deltas is an orange edit. A wide band of those pixels is a local mismatch: it is highlighted in place and does not discard the rest of the page. Unmatched baseline rows are red deletions. Current rows with no baseline partner are green insertions. `matched` on this path means the row was mapped and fell below this highlight rule. Exact fingerprint matches remain byte-identical. Aligned metadata records which strategy applied: exact Myers results carry `alignmentStrategy: "exact"` and no `tolerance`; offset-run results carry `alignmentStrategy: "offset"` and the stable-run `tolerance: 32`, while residual local-mismatch bands are not covered by `tolerance`. An unmatched row that also occurs on the opposite side is ambiguous only when its repeat count on either side stays below `MIN_STRUCTURAL_ROW_REPEATS` (16): such a row could be moved content. Ambiguous exact alignment first tries the offset-run mapping; when that mapping succeeds, the result is `vertical-aligned` with `alignmentStrategy: "offset"`, and only an unsuccessful mapping falls back to v1 coordinate comparison as `ambiguous`. A row that repeats at least 16 times on either side (page background, card padding, borders) is structural and does not count: where it lands inside a run of identical rows makes no visible difference. Offset-run plans treat blank structural padding as matched rather than painting it green or red: a one-sided row carries content only when at least `CONTENT_INK_MIN` (16) full-width ink pixels are present. A one-sided content row that duplicates a claimed row in the other image within `STRUCTURAL_DUPLICATE_COST` (0.05 row ink cost) at an offset within `STRUCTURAL_DUPLICATE_WINDOW` (3) of the neighboring stable runs is a compensating rendering band, not a new or deleted line, and stays matched. An unresolved band at a shift boundary splits into changed-ink rows and padding: a baseline ink row pairs with the current row at the preceding stable offset when that row carries ink and is free, otherwise with its best evidence offset when that offset sits within the duplicate window of the neighboring runs, otherwise it is a red deletion. Leading blank rows stay with the preceding offset only when every candidate current row is free; trailing blank rows follow the last paired content offset when free, otherwise they are the deleted region's blank tail. A candidate current row already claimed by a stable run is never a free partner. Inside a paired row, a mismatch set with fewer than `CONTENT_INK_MIN` mismatched pixels is a localized rendering displacement (a glyph or badge edge drawn a few rows off its text) only when every mismatch has both background evidence — the baseline pixel exists in a claimed current row — and foreground evidence — the current pixel exists in a claimed baseline row — at one common non-zero relative offset within the duplicate window; inserted current rows and deleted baseline rows count as claimed rows on their respective sides. A mismatch with only one side's duplicate, such as a new small mark on a uniform background whose old background has a neighboring current copy but whose foreground exists nowhere in the baseline, is content and stays orange. A contiguous displaced run is suppressed only when it borders matched rows or blank structural one-sided rows; a run bordering unexplained mismatched content or content-carrying one-sided rows stays painted as part of that edit. A mismatch set reaching `CONTENT_INK_MIN` is a structured glyph or region recolor and stays orange. Width changes, ignore regions, resource limits, and ambiguous exact-row matches whose offset-run mapping also fails fall back to v1 coordinate comparison and record the fallback reason; coordinate-fallback metadata carries neither `alignmentStrategy` nor `tolerance`. This is a raster row alignment aid, not DOM or arbitrary component matching.
343
350
  - Both policies preserve the explicit dimension-change signal: a source height/width change remains a changed route even when the aligned mismatch ratio is at or below the threshold. Empty output corners do not enter the denominator.
344
351
  - Local diff images are rendered with a semantic palette: orange = changed pixels, green = added or inserted pixels, red = removed or deleted pixels. Equal-dimension strict diffs (`generateDiffImage`) use orange for changed pixels and cannot contain added/removed pixels. Reports render a legend whenever a local diff image is embedded; summaries that carry only comparison metadata (Snap provider runs, which render diffs hosted-side) render no legend.
345
352
  - `dimensionChanges[]` remains part of the summary contract but is empty for local comparisons in v1. Strict same-dimension comparison (`compareBuffers`) remains available to direct `@snapdrift/compare-core`/`comparePngs` callers that pass no policy.
@@ -393,7 +400,19 @@ since a caller may raise it per call.
393
400
  Policy v2 uses the aligned output height for this same budget. Because an
394
401
  inserted row and a deleted row each occupy an output row, that height can be up
395
402
  to the sum of the baseline and current heights. If the aligned canvas exceeds
396
- the budget, v2 returns `comparison_too_large` before allocating a diff image.
403
+ the budget, both the exact-row alignment path and the offset-run path fall back
404
+ to coordinate comparison with `fallbackReason: 'alignment-limit'`; the
405
+ coordinate union must still fit, and an oversized coordinate union still throws
406
+ `comparison_too_large` before allocating a diff image.
407
+
408
+ The offset-run scoring pass is separately bounded by `MAX_OFFSET_SCORE_VISITS`
409
+ (2.5e9). Its visit count is `baselineHeight × 201 offsets ×
410
+ ceil(baselineWidth / 2)` (sampling every second column, offsets `-40..160`);
411
+ above that budget, or when the per-row cost table and Viterbi backpointers exceed
412
+ their byte ceiling, the offset-run path falls back to coordinate comparison with
413
+ `fallbackReason: 'alignment-limit'`. The 2.5e9 budget admits a 1170×17187 mobile
414
+ pair (~2.02e9 visits) that the previous 1.5e9 budget rejected, while the
415
+ 1440×3386 desktop pair stays at ~0.49e9.
397
416
 
398
417
  `32 × 1024 × 1024` is the *default* ceiling, not an algorithm property. The cost
399
418
  is dominated by the decoded images: both RGBA inputs (4 bytes per pixel each) are
@@ -46,7 +46,7 @@ SnapDrift owns route selection, capture, comparison, skipped-report generation,
46
46
 
47
47
  ```yaml
48
48
  - name: SnapDrift Baseline
49
- uses: ranacseruet/snapdrift@v0.16.1
49
+ uses: ranacseruet/snapdrift@v0.17.0
50
50
  with:
51
51
  mode: baseline
52
52
  repo-config-path: .github/snapdrift.json
@@ -71,7 +71,7 @@ Then add SnapDrift after the app is running:
71
71
 
72
72
  ```yaml
73
73
  - name: SnapDrift Report
74
- uses: ranacseruet/snapdrift@v0.16.1
74
+ uses: ranacseruet/snapdrift@v0.17.0
75
75
  with:
76
76
  mode: pr-diff
77
77
  github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -86,8 +86,8 @@ The wrappers remain published in their own right, and are equivalent to the disp
86
86
 
87
87
  | Entry point | Equivalent to |
88
88
  |-------------|---------------|
89
- | `ranacseruet/snapdrift@v0.16.1` with `mode: baseline` | `ranacseruet/snapdrift/actions/baseline@v0.16.1` |
90
- | `ranacseruet/snapdrift@v0.16.1` with `mode: pr-diff` | `ranacseruet/snapdrift/actions/pr-diff@v0.16.1` |
89
+ | `ranacseruet/snapdrift@v0.17.0` with `mode: baseline` | `ranacseruet/snapdrift/actions/baseline@v0.17.0` |
90
+ | `ranacseruet/snapdrift@v0.17.0` with `mode: pr-diff` | `ranacseruet/snapdrift/actions/pr-diff@v0.17.0` |
91
91
 
92
92
  Use the wrappers directly when you want to skip the dispatcher's input indirection, and the lower-level `capture`, `compare`, `scope`, `resolve-baseline`, `stage`, `comment`, and `enforce` actions when you need to orchestrate the stages yourself.
93
93
 
@@ -130,7 +130,7 @@ jobs:
130
130
  done
131
131
 
132
132
  - name: SnapDrift Report
133
- uses: ranacseruet/snapdrift@v0.16.1
133
+ uses: ranacseruet/snapdrift@v0.17.0
134
134
  with:
135
135
  mode: pr-diff
136
136
  github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -170,7 +170,7 @@ jobs:
170
170
  done
171
171
 
172
172
  - name: SnapDrift Report
173
- uses: ranacseruet/snapdrift@v0.16.1
173
+ uses: ranacseruet/snapdrift@v0.17.0
174
174
  with:
175
175
  mode: pr-diff
176
176
  github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -371,11 +371,12 @@ The Snap client never retries 4xx and never falls back, even when `onUnavailable
371
371
  **Screenshots have different dimensions**
372
372
  SnapDrift compares the top-left-aligned union canvas by default. Policy v2 can
373
373
  align equal-width rows so unchanged content after an insertion is not painted
374
- as drift; width changes, ignored regions, ambiguous rows, and resource limits
375
- fall back to coordinate comparison. Either policy records the route in
376
- `changed[]`, and local runs write a diff PNG. Refresh the baseline after an
377
- intentional change when you want future runs to return to equal-size
378
- comparisons.
374
+ as drift; width changes, ignored regions, ambiguous rows (an inserted or
375
+ deleted row that also appears elsewhere but is not a repeated padding or
376
+ background row), and resource limits fall back to coordinate comparison.
377
+ Either policy records the route in `changed[]`, and local runs write a diff
378
+ PNG. Refresh the baseline after an intentional change when you want future
379
+ runs to return to equal-size comparisons.
379
380
 
380
381
  **A route appears in `errors[]`**
381
382
  Capture failed before comparison. Confirm the app is fully ready and reachable before SnapDrift runs.
@@ -82,7 +82,7 @@ jobs:
82
82
 
83
83
  - name: Publish SnapDrift baseline
84
84
  if: steps.baseline_candidate.outputs.current == 'true'
85
- uses: ranacseruet/snapdrift@v0.16.1
85
+ uses: ranacseruet/snapdrift@v0.17.0
86
86
  with:
87
87
  mode: baseline
88
88
  repo-config-path: .github/snapdrift.json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snapdrift",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "SnapDrift captures, compares, and reports UI drift for Playwright-based app checks.",
@@ -103,9 +103,9 @@
103
103
  "registry": "https://registry.npmjs.org"
104
104
  },
105
105
  "dependencies": {
106
- "@snapdrift/adapter-fs": "^1.7.1",
106
+ "@snapdrift/adapter-fs": "^1.7.2",
107
107
  "@snapdrift/adapter-report-md": "^1.4.0",
108
- "@snapdrift/compare-core": "^1.7.0",
108
+ "@snapdrift/compare-core": "^1.8.0",
109
109
  "@snapdrift/manifest": "^1.8.0",
110
110
  "js-yaml": "^5.4.0",
111
111
  "playwright": "^1.59.1",
@@ -41,6 +41,20 @@ export interface ComparisonMetadata {
41
41
  totalPixels: number;
42
42
  policyVersion?: 2;
43
43
  mode?: 'vertical-aligned' | 'coordinate-fallback';
44
+ /**
45
+ * Strategy that produced an aligned result. `exact` matched byte-identical
46
+ * rows with no tolerance; `offset` mapped piecewise vertical-offset runs and
47
+ * used a per-channel highlight tolerance for stable runs. Absent for
48
+ * coordinate fallback.
49
+ */
50
+ alignmentStrategy?: 'exact' | 'offset';
51
+ /**
52
+ * Maximum per-channel delta that stays matched on stable offset-run rows
53
+ * (`STRUCTURED_DELTA`, 32). Local-mismatch residual bands use `NOISE_DELTA`
54
+ * (2) instead, so this field does not describe those rows. Present only with
55
+ * `alignmentStrategy: 'offset'`.
56
+ */
57
+ tolerance?: number;
44
58
  rowMapping?: ComparisonRowMapping[];
45
59
  fallbackReason?: ComparisonFallbackReason;
46
60
  }