fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from - #2373

Open
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets
Open

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from#2373
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets

Conversation

@aryanku-dev

@aryanku-devaryanku-dev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes the diagnosability half of PER-10200.

Problem

On App Automate iOS full-page capture, the device-side percyScreenshot executor can return a negative header_height for a tile. header_height is the number of pixels cropped off the top of a tile before stitching — it removes the fixed device chrome and the band the previous tile already showed.

PercyConfig.validaterepairs out-of-range values instead of rejecting them (packages/config/src/validate.js, keyword === 'minimum'Math.max(data, schema)), and percy.upload() only warns and continues. So the negative silently became 0, the snapshot uploaded, and percy-api cropped nothing off the tile top — stitching the iOS status bar and the app nav bar into the middle of the full-page screenshot. The customer gets a plausible-looking but wrong baseline rather than a visible failure.

The only signal was:

[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0

Two things missing there:

  • No snapshot name.options.meta was assigned after the validation block, so these lines carried no meta. A run that warns about three different pages produces three identical anonymous pairs, with no way to tell which tiles[n] belongs to which page.
  • No value. The number that was actually received never reached the logs, and neither did the fact that it had been substituted.

Diagnosing one such build needed the stored tiles rows out of the DB plus a hand-rebuilt stitch from the raw GCS tiles. That should have been readable from the CLI output.

Change

  • packages/config/src/validate.js — record received / clampedTo on clamp errors. Additive; the other two validate() consumers (percy.js:224, snapshot.js:255) read only path/message and are unaffected.
  • packages/core/src/percy.js — set the logging meta before validating so warnings are attributed to their snapshot, and report the substitution.
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)

The clamp behaviour itself is deliberately unchanged — this only makes it visible. The bad value originates device-side in the App Automate capture routine and is fixed there; rejecting the upload here would turn a wrong screenshot into a failed build for customers whose only recourse is an upstream fix.

Testing

  • packages/core/test/percy.test.js — two new specs: the clamp is reported with the received value (using the real PER-10200 tile geometry), and validation warnings carry their snapshot's meta.
  • packages/config/test/index.test.js — existing clamp assertions updated for the new fields.
  • @percy/config: 82/82 pass. @percy/corepercy.test.js: 137/137 pass. yarn lint clean.

🤖 Generated with Claude Code

…h snapshot it came from
An App Automate iOS fullpage capture can return a negative `header_height`
for a tile. `PercyConfig.validate` repairs out-of-range values rather than
rejecting them, so the negative became 0 and the snapshot uploaded anyway —
with no top crop, which stitches the device status bar and app nav bar into
the middle of the full-page image. The only signal was:
[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0
which names neither the snapshot nor the value that was thrown away, so a run
warning about several pages gives no way to tell them apart. Diagnosing one
such build (PER-10200) needed the stored tile rows and a hand-rebuilt stitch.
Record `received`/`clampedTo` on clamp errors, set the logging meta before
validating so the warnings are attributed to their snapshot, and report the
substitution:
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)
The clamp itself is unchanged — this only makes it visible. The bad value
originates device-side and is fixed there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aryanku-dev
aryanku-dev requested a review from a team as a code ownerAugust 7, 2026 05:40
@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for more than 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actionsgithub-actionsBot added the 🍞 stale Closed due to inactivity label Aug 25, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍞 staleClosed due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@aryanku-dev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from - #2373

Open
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets
Open

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from#2373
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets

Conversation

@aryanku-dev

@aryanku-devaryanku-dev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes the diagnosability half of PER-10200.

Problem

On App Automate iOS full-page capture, the device-side percyScreenshot executor can return a negative header_height for a tile. header_height is the number of pixels cropped off the top of a tile before stitching — it removes the fixed device chrome and the band the previous tile already showed.

PercyConfig.validaterepairs out-of-range values instead of rejecting them (packages/config/src/validate.js, keyword === 'minimum'Math.max(data, schema)), and percy.upload() only warns and continues. So the negative silently became 0, the snapshot uploaded, and percy-api cropped nothing off the tile top — stitching the iOS status bar and the app nav bar into the middle of the full-page screenshot. The customer gets a plausible-looking but wrong baseline rather than a visible failure.

The only signal was:

[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0

Two things missing there:

  • No snapshot name.options.meta was assigned after the validation block, so these lines carried no meta. A run that warns about three different pages produces three identical anonymous pairs, with no way to tell which tiles[n] belongs to which page.
  • No value. The number that was actually received never reached the logs, and neither did the fact that it had been substituted.

Diagnosing one such build needed the stored tiles rows out of the DB plus a hand-rebuilt stitch from the raw GCS tiles. That should have been readable from the CLI output.

Change

  • packages/config/src/validate.js — record received / clampedTo on clamp errors. Additive; the other two validate() consumers (percy.js:224, snapshot.js:255) read only path/message and are unaffected.
  • packages/core/src/percy.js — set the logging meta before validating so warnings are attributed to their snapshot, and report the substitution.
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)

The clamp behaviour itself is deliberately unchanged — this only makes it visible. The bad value originates device-side in the App Automate capture routine and is fixed there; rejecting the upload here would turn a wrong screenshot into a failed build for customers whose only recourse is an upstream fix.

Testing

  • packages/core/test/percy.test.js — two new specs: the clamp is reported with the received value (using the real PER-10200 tile geometry), and validation warnings carry their snapshot's meta.
  • packages/config/test/index.test.js — existing clamp assertions updated for the new fields.
  • @percy/config: 82/82 pass. @percy/corepercy.test.js: 137/137 pass. yarn lint clean.

🤖 Generated with Claude Code

…h snapshot it came from
An App Automate iOS fullpage capture can return a negative `header_height`
for a tile. `PercyConfig.validate` repairs out-of-range values rather than
rejecting them, so the negative became 0 and the snapshot uploaded anyway —
with no top crop, which stitches the device status bar and app nav bar into
the middle of the full-page image. The only signal was:
[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0
which names neither the snapshot nor the value that was thrown away, so a run
warning about several pages gives no way to tell them apart. Diagnosing one
such build (PER-10200) needed the stored tile rows and a hand-rebuilt stitch.
Record `received`/`clampedTo` on clamp errors, set the logging meta before
validating so the warnings are attributed to their snapshot, and report the
substitution:
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)
The clamp itself is unchanged — this only makes it visible. The bad value
originates device-side and is fixed there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aryanku-dev
aryanku-dev requested a review from a team as a code ownerAugust 7, 2026 05:40
@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for more than 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actionsgithub-actionsBot added the 🍞 stale Closed due to inactivity label Aug 25, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍞 staleClosed due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@aryanku-dev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from - #2373

Open
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets
Open

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from#2373
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets

Conversation

@aryanku-dev

@aryanku-devaryanku-dev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes the diagnosability half of PER-10200.

Problem

On App Automate iOS full-page capture, the device-side percyScreenshot executor can return a negative header_height for a tile. header_height is the number of pixels cropped off the top of a tile before stitching — it removes the fixed device chrome and the band the previous tile already showed.

PercyConfig.validaterepairs out-of-range values instead of rejecting them (packages/config/src/validate.js, keyword === 'minimum'Math.max(data, schema)), and percy.upload() only warns and continues. So the negative silently became 0, the snapshot uploaded, and percy-api cropped nothing off the tile top — stitching the iOS status bar and the app nav bar into the middle of the full-page screenshot. The customer gets a plausible-looking but wrong baseline rather than a visible failure.

The only signal was:

[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0

Two things missing there:

  • No snapshot name.options.meta was assigned after the validation block, so these lines carried no meta. A run that warns about three different pages produces three identical anonymous pairs, with no way to tell which tiles[n] belongs to which page.
  • No value. The number that was actually received never reached the logs, and neither did the fact that it had been substituted.

Diagnosing one such build needed the stored tiles rows out of the DB plus a hand-rebuilt stitch from the raw GCS tiles. That should have been readable from the CLI output.

Change

  • packages/config/src/validate.js — record received / clampedTo on clamp errors. Additive; the other two validate() consumers (percy.js:224, snapshot.js:255) read only path/message and are unaffected.
  • packages/core/src/percy.js — set the logging meta before validating so warnings are attributed to their snapshot, and report the substitution.
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)

The clamp behaviour itself is deliberately unchanged — this only makes it visible. The bad value originates device-side in the App Automate capture routine and is fixed there; rejecting the upload here would turn a wrong screenshot into a failed build for customers whose only recourse is an upstream fix.

Testing

  • packages/core/test/percy.test.js — two new specs: the clamp is reported with the received value (using the real PER-10200 tile geometry), and validation warnings carry their snapshot's meta.
  • packages/config/test/index.test.js — existing clamp assertions updated for the new fields.
  • @percy/config: 82/82 pass. @percy/corepercy.test.js: 137/137 pass. yarn lint clean.

🤖 Generated with Claude Code

…h snapshot it came from
An App Automate iOS fullpage capture can return a negative `header_height`
for a tile. `PercyConfig.validate` repairs out-of-range values rather than
rejecting them, so the negative became 0 and the snapshot uploaded anyway —
with no top crop, which stitches the device status bar and app nav bar into
the middle of the full-page image. The only signal was:
[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0
which names neither the snapshot nor the value that was thrown away, so a run
warning about several pages gives no way to tell them apart. Diagnosing one
such build (PER-10200) needed the stored tile rows and a hand-rebuilt stitch.
Record `received`/`clampedTo` on clamp errors, set the logging meta before
validating so the warnings are attributed to their snapshot, and report the
substitution:
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)
The clamp itself is unchanged — this only makes it visible. The bad value
originates device-side and is fixed there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aryanku-dev
aryanku-dev requested a review from a team as a code ownerAugust 7, 2026 05:40
@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for more than 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actionsgithub-actionsBot added the 🍞 stale Closed due to inactivity label Aug 25, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍞 staleClosed due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@aryanku-dev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from - #2373

Open
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets
Open

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from#2373
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets

Conversation

@aryanku-dev

@aryanku-devaryanku-dev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes the diagnosability half of PER-10200.

Problem

On App Automate iOS full-page capture, the device-side percyScreenshot executor can return a negative header_height for a tile. header_height is the number of pixels cropped off the top of a tile before stitching — it removes the fixed device chrome and the band the previous tile already showed.

PercyConfig.validaterepairs out-of-range values instead of rejecting them (packages/config/src/validate.js, keyword === 'minimum'Math.max(data, schema)), and percy.upload() only warns and continues. So the negative silently became 0, the snapshot uploaded, and percy-api cropped nothing off the tile top — stitching the iOS status bar and the app nav bar into the middle of the full-page screenshot. The customer gets a plausible-looking but wrong baseline rather than a visible failure.

The only signal was:

[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0

Two things missing there:

  • No snapshot name.options.meta was assigned after the validation block, so these lines carried no meta. A run that warns about three different pages produces three identical anonymous pairs, with no way to tell which tiles[n] belongs to which page.
  • No value. The number that was actually received never reached the logs, and neither did the fact that it had been substituted.

Diagnosing one such build needed the stored tiles rows out of the DB plus a hand-rebuilt stitch from the raw GCS tiles. That should have been readable from the CLI output.

Change

  • packages/config/src/validate.js — record received / clampedTo on clamp errors. Additive; the other two validate() consumers (percy.js:224, snapshot.js:255) read only path/message and are unaffected.
  • packages/core/src/percy.js — set the logging meta before validating so warnings are attributed to their snapshot, and report the substitution.
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)

The clamp behaviour itself is deliberately unchanged — this only makes it visible. The bad value originates device-side in the App Automate capture routine and is fixed there; rejecting the upload here would turn a wrong screenshot into a failed build for customers whose only recourse is an upstream fix.

Testing

  • packages/core/test/percy.test.js — two new specs: the clamp is reported with the received value (using the real PER-10200 tile geometry), and validation warnings carry their snapshot's meta.
  • packages/config/test/index.test.js — existing clamp assertions updated for the new fields.
  • @percy/config: 82/82 pass. @percy/corepercy.test.js: 137/137 pass. yarn lint clean.

🤖 Generated with Claude Code

…h snapshot it came from
An App Automate iOS fullpage capture can return a negative `header_height`
for a tile. `PercyConfig.validate` repairs out-of-range values rather than
rejecting them, so the negative became 0 and the snapshot uploaded anyway —
with no top crop, which stitches the device status bar and app nav bar into
the middle of the full-page image. The only signal was:
[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0
which names neither the snapshot nor the value that was thrown away, so a run
warning about several pages gives no way to tell them apart. Diagnosing one
such build (PER-10200) needed the stored tile rows and a hand-rebuilt stitch.
Record `received`/`clampedTo` on clamp errors, set the logging meta before
validating so the warnings are attributed to their snapshot, and report the
substitution:
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)
The clamp itself is unchanged — this only makes it visible. The bad value
originates device-side and is fixed there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aryanku-dev
aryanku-dev requested a review from a team as a code ownerAugust 7, 2026 05:40
@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for more than 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actionsgithub-actionsBot added the 🍞 stale Closed due to inactivity label Aug 25, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍞 staleClosed due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@aryanku-dev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from - #2373

Open
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets
Open

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from#2373
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets

Conversation

@aryanku-dev

@aryanku-devaryanku-dev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes the diagnosability half of PER-10200.

Problem

On App Automate iOS full-page capture, the device-side percyScreenshot executor can return a negative header_height for a tile. header_height is the number of pixels cropped off the top of a tile before stitching — it removes the fixed device chrome and the band the previous tile already showed.

PercyConfig.validaterepairs out-of-range values instead of rejecting them (packages/config/src/validate.js, keyword === 'minimum'Math.max(data, schema)), and percy.upload() only warns and continues. So the negative silently became 0, the snapshot uploaded, and percy-api cropped nothing off the tile top — stitching the iOS status bar and the app nav bar into the middle of the full-page screenshot. The customer gets a plausible-looking but wrong baseline rather than a visible failure.

The only signal was:

[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0

Two things missing there:

  • No snapshot name.options.meta was assigned after the validation block, so these lines carried no meta. A run that warns about three different pages produces three identical anonymous pairs, with no way to tell which tiles[n] belongs to which page.
  • No value. The number that was actually received never reached the logs, and neither did the fact that it had been substituted.

Diagnosing one such build needed the stored tiles rows out of the DB plus a hand-rebuilt stitch from the raw GCS tiles. That should have been readable from the CLI output.

Change

  • packages/config/src/validate.js — record received / clampedTo on clamp errors. Additive; the other two validate() consumers (percy.js:224, snapshot.js:255) read only path/message and are unaffected.
  • packages/core/src/percy.js — set the logging meta before validating so warnings are attributed to their snapshot, and report the substitution.
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)

The clamp behaviour itself is deliberately unchanged — this only makes it visible. The bad value originates device-side in the App Automate capture routine and is fixed there; rejecting the upload here would turn a wrong screenshot into a failed build for customers whose only recourse is an upstream fix.

Testing

  • packages/core/test/percy.test.js — two new specs: the clamp is reported with the received value (using the real PER-10200 tile geometry), and validation warnings carry their snapshot's meta.
  • packages/config/test/index.test.js — existing clamp assertions updated for the new fields.
  • @percy/config: 82/82 pass. @percy/corepercy.test.js: 137/137 pass. yarn lint clean.

🤖 Generated with Claude Code

…h snapshot it came from
An App Automate iOS fullpage capture can return a negative `header_height`
for a tile. `PercyConfig.validate` repairs out-of-range values rather than
rejecting them, so the negative became 0 and the snapshot uploaded anyway —
with no top crop, which stitches the device status bar and app nav bar into
the middle of the full-page image. The only signal was:
[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0
which names neither the snapshot nor the value that was thrown away, so a run
warning about several pages gives no way to tell them apart. Diagnosing one
such build (PER-10200) needed the stored tile rows and a hand-rebuilt stitch.
Record `received`/`clampedTo` on clamp errors, set the logging meta before
validating so the warnings are attributed to their snapshot, and report the
substitution:
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)
The clamp itself is unchanged — this only makes it visible. The bad value
originates device-side and is fixed there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aryanku-dev
aryanku-dev requested a review from a team as a code ownerAugust 7, 2026 05:40
@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for more than 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actionsgithub-actionsBot added the 🍞 stale Closed due to inactivity label Aug 25, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍞 staleClosed due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@aryanku-dev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from - #2373

Open
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets
Open

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from#2373
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets

Conversation

@aryanku-dev

@aryanku-devaryanku-dev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes the diagnosability half of PER-10200.

Problem

On App Automate iOS full-page capture, the device-side percyScreenshot executor can return a negative header_height for a tile. header_height is the number of pixels cropped off the top of a tile before stitching — it removes the fixed device chrome and the band the previous tile already showed.

PercyConfig.validaterepairs out-of-range values instead of rejecting them (packages/config/src/validate.js, keyword === 'minimum'Math.max(data, schema)), and percy.upload() only warns and continues. So the negative silently became 0, the snapshot uploaded, and percy-api cropped nothing off the tile top — stitching the iOS status bar and the app nav bar into the middle of the full-page screenshot. The customer gets a plausible-looking but wrong baseline rather than a visible failure.

The only signal was:

[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0

Two things missing there:

  • No snapshot name.options.meta was assigned after the validation block, so these lines carried no meta. A run that warns about three different pages produces three identical anonymous pairs, with no way to tell which tiles[n] belongs to which page.
  • No value. The number that was actually received never reached the logs, and neither did the fact that it had been substituted.

Diagnosing one such build needed the stored tiles rows out of the DB plus a hand-rebuilt stitch from the raw GCS tiles. That should have been readable from the CLI output.

Change

  • packages/config/src/validate.js — record received / clampedTo on clamp errors. Additive; the other two validate() consumers (percy.js:224, snapshot.js:255) read only path/message and are unaffected.
  • packages/core/src/percy.js — set the logging meta before validating so warnings are attributed to their snapshot, and report the substitution.
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)

The clamp behaviour itself is deliberately unchanged — this only makes it visible. The bad value originates device-side in the App Automate capture routine and is fixed there; rejecting the upload here would turn a wrong screenshot into a failed build for customers whose only recourse is an upstream fix.

Testing

  • packages/core/test/percy.test.js — two new specs: the clamp is reported with the received value (using the real PER-10200 tile geometry), and validation warnings carry their snapshot's meta.
  • packages/config/test/index.test.js — existing clamp assertions updated for the new fields.
  • @percy/config: 82/82 pass. @percy/corepercy.test.js: 137/137 pass. yarn lint clean.

🤖 Generated with Claude Code

…h snapshot it came from
An App Automate iOS fullpage capture can return a negative `header_height`
for a tile. `PercyConfig.validate` repairs out-of-range values rather than
rejecting them, so the negative became 0 and the snapshot uploaded anyway —
with no top crop, which stitches the device status bar and app nav bar into
the middle of the full-page image. The only signal was:
[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0
which names neither the snapshot nor the value that was thrown away, so a run
warning about several pages gives no way to tell them apart. Diagnosing one
such build (PER-10200) needed the stored tile rows and a hand-rebuilt stitch.
Record `received`/`clampedTo` on clamp errors, set the logging meta before
validating so the warnings are attributed to their snapshot, and report the
substitution:
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)
The clamp itself is unchanged — this only makes it visible. The bad value
originates device-side and is fixed there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aryanku-dev
aryanku-dev requested a review from a team as a code ownerAugust 7, 2026 05:40
@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for more than 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actionsgithub-actionsBot added the 🍞 stale Closed due to inactivity label Aug 25, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍞 staleClosed due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@aryanku-dev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from - #2373

Open
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets
Open

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from#2373
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets

Conversation

@aryanku-dev

@aryanku-devaryanku-dev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes the diagnosability half of PER-10200.

Problem

On App Automate iOS full-page capture, the device-side percyScreenshot executor can return a negative header_height for a tile. header_height is the number of pixels cropped off the top of a tile before stitching — it removes the fixed device chrome and the band the previous tile already showed.

PercyConfig.validaterepairs out-of-range values instead of rejecting them (packages/config/src/validate.js, keyword === 'minimum'Math.max(data, schema)), and percy.upload() only warns and continues. So the negative silently became 0, the snapshot uploaded, and percy-api cropped nothing off the tile top — stitching the iOS status bar and the app nav bar into the middle of the full-page screenshot. The customer gets a plausible-looking but wrong baseline rather than a visible failure.

The only signal was:

[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0

Two things missing there:

  • No snapshot name.options.meta was assigned after the validation block, so these lines carried no meta. A run that warns about three different pages produces three identical anonymous pairs, with no way to tell which tiles[n] belongs to which page.
  • No value. The number that was actually received never reached the logs, and neither did the fact that it had been substituted.

Diagnosing one such build needed the stored tiles rows out of the DB plus a hand-rebuilt stitch from the raw GCS tiles. That should have been readable from the CLI output.

Change

  • packages/config/src/validate.js — record received / clampedTo on clamp errors. Additive; the other two validate() consumers (percy.js:224, snapshot.js:255) read only path/message and are unaffected.
  • packages/core/src/percy.js — set the logging meta before validating so warnings are attributed to their snapshot, and report the substitution.
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)

The clamp behaviour itself is deliberately unchanged — this only makes it visible. The bad value originates device-side in the App Automate capture routine and is fixed there; rejecting the upload here would turn a wrong screenshot into a failed build for customers whose only recourse is an upstream fix.

Testing

  • packages/core/test/percy.test.js — two new specs: the clamp is reported with the received value (using the real PER-10200 tile geometry), and validation warnings carry their snapshot's meta.
  • packages/config/test/index.test.js — existing clamp assertions updated for the new fields.
  • @percy/config: 82/82 pass. @percy/corepercy.test.js: 137/137 pass. yarn lint clean.

🤖 Generated with Claude Code

…h snapshot it came from
An App Automate iOS fullpage capture can return a negative `header_height`
for a tile. `PercyConfig.validate` repairs out-of-range values rather than
rejecting them, so the negative became 0 and the snapshot uploaded anyway —
with no top crop, which stitches the device status bar and app nav bar into
the middle of the full-page image. The only signal was:
[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0
which names neither the snapshot nor the value that was thrown away, so a run
warning about several pages gives no way to tell them apart. Diagnosing one
such build (PER-10200) needed the stored tile rows and a hand-rebuilt stitch.
Record `received`/`clampedTo` on clamp errors, set the logging meta before
validating so the warnings are attributed to their snapshot, and report the
substitution:
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)
The clamp itself is unchanged — this only makes it visible. The bad value
originates device-side and is fixed there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aryanku-dev
aryanku-dev requested a review from a team as a code ownerAugust 7, 2026 05:40
@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for more than 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actionsgithub-actionsBot added the 🍞 stale Closed due to inactivity label Aug 25, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍞 staleClosed due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@aryanku-dev
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from - #2373

Open
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets
Open

fix(core): report the tile offset a clamped upload replaced, and which snapshot it came from#2373
aryanku-dev wants to merge 1 commit into
masterfrom
fix/PER-10200-report-clamped-tile-offsets

Conversation

@aryanku-dev

@aryanku-devaryanku-dev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes the diagnosability half of PER-10200.

Problem

On App Automate iOS full-page capture, the device-side percyScreenshot executor can return a negative header_height for a tile. header_height is the number of pixels cropped off the top of a tile before stitching — it removes the fixed device chrome and the band the previous tile already showed.

PercyConfig.validaterepairs out-of-range values instead of rejecting them (packages/config/src/validate.js, keyword === 'minimum'Math.max(data, schema)), and percy.upload() only warns and continues. So the negative silently became 0, the snapshot uploaded, and percy-api cropped nothing off the tile top — stitching the iOS status bar and the app nav bar into the middle of the full-page screenshot. The customer gets a plausible-looking but wrong baseline rather than a visible failure.

The only signal was:

[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0

Two things missing there:

  • No snapshot name.options.meta was assigned after the validation block, so these lines carried no meta. A run that warns about three different pages produces three identical anonymous pairs, with no way to tell which tiles[n] belongs to which page.
  • No value. The number that was actually received never reached the logs, and neither did the fact that it had been substituted.

Diagnosing one such build needed the stored tiles rows out of the DB plus a hand-rebuilt stitch from the raw GCS tiles. That should have been readable from the CLI output.

Change

  • packages/config/src/validate.js — record received / clampedTo on clamp errors. Additive; the other two validate() consumers (percy.js:224, snapshot.js:255) read only path/message and are unaffected.
  • packages/core/src/percy.js — set the logging meta before validating so warnings are attributed to their snapshot, and report the substitution.
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)

The clamp behaviour itself is deliberately unchanged — this only makes it visible. The bad value originates device-side in the App Automate capture routine and is fixed there; rejecting the upload here would turn a wrong screenshot into a failed build for customers whose only recourse is an upstream fix.

Testing

  • packages/core/test/percy.test.js — two new specs: the clamp is reported with the received value (using the real PER-10200 tile geometry), and validation warnings carry their snapshot's meta.
  • packages/config/test/index.test.js — existing clamp assertions updated for the new fields.
  • @percy/config: 82/82 pass. @percy/corepercy.test.js: 137/137 pass. yarn lint clean.

🤖 Generated with Claude Code

…h snapshot it came from
An App Automate iOS fullpage capture can return a negative `header_height`
for a tile. `PercyConfig.validate` repairs out-of-range values rather than
rejecting them, so the negative became 0 and the snapshot uploaded anyway —
with no top crop, which stitches the device status bar and app nav bar into
the middle of the full-page image. The only signal was:
[percy] Invalid upload options:
[percy] - tiles[1].headerHeight: must be >= 0
which names neither the snapshot nor the value that was thrown away, so a run
warning about several pages gives no way to tell them apart. Diagnosing one
such build (PER-10200) needed the stored tile rows and a hand-rebuilt stitch.
Record `received`/`clampedTo` on clamp errors, set the logging meta before
validating so the warnings are attributed to their snapshot, and report the
substitution:
[percy] - tiles[1].headerHeight: must be >= 0 (received -318, continuing with 0)
The clamp itself is unchanged — this only makes it visible. The bad value
originates device-side and is fixed there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aryanku-dev
aryanku-dev requested a review from a team as a code ownerAugust 7, 2026 05:40
@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for more than 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actionsgithub-actionsBot added the 🍞 stale Closed due to inactivity label Aug 25, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍞 staleClosed due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@aryanku-dev