Skip to content

Commit 2860f63

Browse files
authored
Merge pull request #51 from DiffyWebsite/diffy-skills
fix scroll stabilization
2 parents 827c405 + 7d51054 commit 2860f63

4 files changed

Lines changed: 38 additions & 2 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"name": "diffy",
1313
"source": "./plugins/diffy",
1414
"description": "Create Diffy projects, capture/upload screenshots, and run visual diffs.",
15-
"version": "1.0.2",
15+
"version": "1.0.3",
1616
"author": {
1717
"name": "Diffy"
1818
},

plugins/diffy/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "diffy",
33
"description": "Diffy visual-regression skills: create projects, capture/upload screenshots, and run visual diffs via the diffy CLI.",
4-
"version": "1.0.2",
4+
"version": "1.0.3",
55
"author": {
66
"name": "Diffy",
77
"email": "support@diffy.website"

plugins/diffy/skills/upload-screenshot/scripts/capture.mjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ async function stabilize(page, { scroll, settle, freeze }) {
209209
// which leaves whole sections blank. Bounded so infinite-scroll pages can't loop forever.
210210
if (scroll) {
211211
await page.evaluate(async () => {
212+
// Force instant scrolling before we move. With the site's `scroll-behavior: smooth`,
213+
// the return-to-top below would animate and `resolve()` fires without awaiting it,
214+
// leaving a residual scroll offset that misplaces sticky/fixed navbars and scroll-linked
215+
// transforms in the full-page shot. This override persists for the final re-assert too.
216+
document.documentElement.style.scrollBehavior = 'auto';
212217
await new Promise((resolve) => {
213218
const step = Math.max(200, Math.floor(window.innerHeight * 0.8));
214219
const cap = 200000; // safety cap in px for infinite-scroll pages
@@ -267,6 +272,19 @@ async function stabilize(page, { scroll, settle, freeze }) {
267272
} else {
268273
await page.waitForTimeout(500);
269274
}
275+
276+
// 6. Re-assert the top of the page as the very last thing before capture. The scroll pass
277+
// can leave a residual offset, and scroll-driven UI reacts to it — sticky/fixed navbars
278+
// (which a full-page screenshot then paints floating mid-image) and `useScroll`-linked
279+
// transforms. scroll-behavior was set to `auto` in step 1, so this lands instantly; then
280+
// let the scroll listeners re-render (e.g. a navbar flipping back from fixed to relative)
281+
// and any resulting reveal settle before the shot.
282+
await page.evaluate(() => window.scrollTo(0, 0));
283+
if (freeze) {
284+
await waitForVisualStability(page, { timeout: Math.min(settle, 3000) });
285+
} else {
286+
await page.waitForTimeout(500);
287+
}
270288
}
271289

272290
// ---- capture ---------------------------------------------------------------

plugins/diffy/skills/visual-diff/scripts/capture.mjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ async function stabilize(page, { scroll, settle, freeze }) {
209209
// which leaves whole sections blank. Bounded so infinite-scroll pages can't loop forever.
210210
if (scroll) {
211211
await page.evaluate(async () => {
212+
// Force instant scrolling before we move. With the site's `scroll-behavior: smooth`,
213+
// the return-to-top below would animate and `resolve()` fires without awaiting it,
214+
// leaving a residual scroll offset that misplaces sticky/fixed navbars and scroll-linked
215+
// transforms in the full-page shot. This override persists for the final re-assert too.
216+
document.documentElement.style.scrollBehavior = 'auto';
212217
await new Promise((resolve) => {
213218
const step = Math.max(200, Math.floor(window.innerHeight * 0.8));
214219
const cap = 200000; // safety cap in px for infinite-scroll pages
@@ -267,6 +272,19 @@ async function stabilize(page, { scroll, settle, freeze }) {
267272
} else {
268273
await page.waitForTimeout(500);
269274
}
275+
276+
// 6. Re-assert the top of the page as the very last thing before capture. The scroll pass
277+
// can leave a residual offset, and scroll-driven UI reacts to it — sticky/fixed navbars
278+
// (which a full-page screenshot then paints floating mid-image) and `useScroll`-linked
279+
// transforms. scroll-behavior was set to `auto` in step 1, so this lands instantly; then
280+
// let the scroll listeners re-render (e.g. a navbar flipping back from fixed to relative)
281+
// and any resulting reveal settle before the shot.
282+
await page.evaluate(() => window.scrollTo(0, 0));
283+
if (freeze) {
284+
await waitForVisualStability(page, { timeout: Math.min(settle, 3000) });
285+
} else {
286+
await page.waitForTimeout(500);
287+
}
270288
}
271289

272290
// ---- capture ---------------------------------------------------------------

0 commit comments

Comments
 (0)