Skip to content

fix(pixel): use ResizeObserver to detect above-fold scroll state (SDK-275)#2868

Merged
bkbooth merged 4 commits into
mainfrom
claude/confident-austin-52c01c
May 1, 2026
Merged

fix(pixel): use ResizeObserver to detect above-fold scroll state (SDK-275)#2868
bkbooth merged 4 commits into
mainfrom
claude/confident-austin-52c01c

Conversation

@bkbooth
Copy link
Copy Markdown
Contributor

@bkbooth bkbooth commented May 1, 2026

Summary

Fixes SDK-275 — the pixel tracker was firing a false scroll_depth { depth: 100, aboveFold: true } event on long pages when installed on third-party sites.

Root cause: the above-fold determination in autocapture.ts was a synchronous snapshot taken at tracker init time. If the pixel initialised before images or JS-rendered content had expanded the page, scrollHeight could equal innerHeight at that moment — locking in isAboveFold = true and firing the synthetic event ~2s later regardless of how large the page had grown by then.

Fix: replace the one-shot check with a ResizeObserver on document.documentElement that manages the dwell timer reactively. Starts the timer when the page is above-fold, cancels it if the page later grows beyond the viewport, fires once the page has been stably above-fold for the dwell window. Feature-detected so the ~3% of browsers without ResizeObserver skip the synthetic event rather than crashing the rest of autocapture.

See the Linear ticket for the competitive analysis (PostHog, GTM, Segment, robflaherty/scroll-depth) and the option-by-option assessment behind the chosen approach.

Also included

  • AGENTS.md for packages/audience/pixel and packages/audience/core flagging the inline-bundling relationship (core is bundled into the pixel CDN snippet via tsup.config.ts alias) and pointing at the CI-enforced size budget. core changes silently affect the pixel's bundle size and there was no way for an agent or new contributor to discover that without reading the build config.

Test plan

  • Existing scroll-depth tests pass unchanged
  • New test: dwell timer is cancelled when page grows beyond viewport mid-dwell
  • New test: setupAutocapture does not throw when ResizeObserver is undefined (graceful degradation)
  • Lint clean
  • Manual verification on a third-party-style page with images that load post-init (reviewer to confirm in staging if desired)

🤖 Generated with Claude Code


Note

Medium Risk
Changes production analytics emission logic for scroll_depth on above-the-fold pages and adds a new dependency on ResizeObserver behavior (with a fallback when unavailable), which could affect event firing timing/volume in the pixel snippet.

Overview
Fixes false scroll_depth { depth: 100, aboveFold: true } events by replacing the one-time “above fold” snapshot with a ResizeObserver-driven check that starts the dwell timer only while the document remains within the viewport and cancels it if the page grows.

Updates tests to stub/mimic ResizeObserver, adds coverage for cancelling the dwell timer on late content expansion, and verifies graceful behavior when ResizeObserver is missing. Also bumps @imtbl/pixel to 0.1.1 and adds AGENTS.md notes documenting the pixel bundle-size budget and that @imtbl/audience-core is bundled inline into the pixel.

Reviewed by Cursor Bugbot for commit d6c497f. Bugbot is set up for automated code reviews on this repo. Configure here.

…-275)

The above-fold check was a synchronous snapshot taken at tracker init
time. On third-party sites that initialise the pixel before images or
JS-rendered content has expanded the page, scrollHeight could equal
innerHeight at that moment — locking in isAboveFold = true and firing
a false scroll_depth { depth: 100, aboveFold: true } event ~2s later
even on long pages.

Replace the one-shot check with a ResizeObserver on documentElement
that manages the dwell timer reactively: starts when the page is
above-fold, cancels if the page later grows, fires once stable for
the dwell window. Feature-detected to avoid breaking autocapture
on the ~3% of browsers without ResizeObserver.

Also adds AGENTS.md to packages/audience/{pixel,core} flagging the
inline-bundling relationship and CI bundle-size enforcement, since
core changes silently affect the pixel CDN bundle's size budget.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bkbooth bkbooth requested a review from a team as a code owner May 1, 2026 01:31
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

✅ Pixel Bundle Size — @imtbl/pixel

Metric Size Delta vs main
Gzipped 5884 bytes (5.74 KB) +89 bytes
Raw (minified) 15885 bytes +327 bytes

Budget: 10.00 KB gzipped (warn at 8.00 KB)

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 1, 2026

View your CI Pipeline Execution ↗ for commit d6c497f

Command Status Duration Result
nx affected -t build,lint,test ✅ Succeeded 8s View ↗
nx run-many -p @imtbl/sdk,@imtbl/checkout-widge... ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-01 02:03:02 UTC

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

✅ Audience Bundle Size — @imtbl/audience

Metric Size Delta vs main (756ce14)
Gzipped 18249 bytes (17.82 KB) 0 bytes
Raw (minified) 53041 bytes 0 bytes

Budget: 24.00 KB gzipped (warn at 20.00 KB)

bkbooth and others added 3 commits May 1, 2026 11:35
Local rebuilds are useful for fast iteration while cutting bytes;
the CI workflow remains the source of truth.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The link to bundlebudget.json is enough; inlining the values risks
staleness if the budgets are tuned.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Patch bump for the ResizeObserver above-fold fix (SDK-275). Version
is compiled into the bundle via PIXEL_VERSION_INJECTED and sent on
every event, allowing the analytics backend to segment by pixel version.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bkbooth bkbooth added this pull request to the merge queue May 1, 2026
Merged via the queue into main with commit 51a4793 May 1, 2026
12 checks passed
@bkbooth bkbooth deleted the claude/confident-austin-52c01c branch May 1, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants