Skip to content

feat(browser): opt-in long animation frame and slow interaction spans - #1158

Merged
Makisuo merged 6 commits into
feat/browser-sdk-network-canvasfrom
feat/browser-sdk-long-frames
Sep 29, 2026
Merged

Makisuo merged 6 commits into
feat/browser-sdk-network-canvasfrom
feat/browser-sdk-long-frames

Conversation

@Makisuo

@Makisuo Makisuo commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Part 12 of the browser SDK stack. Based on the headers/bodies/canvas PR.

What changes

  • tracing.longFrames spans every frame of 100ms or more as longAnimationFrame, attributed to the script that ran longest (code.file.path, code.function.name, maple.browser.script.invoker, maple.browser.script.duration_ms), plus maple.browser.frame.blocking_duration_ms. Browsers without the Long Animation Frames API get longtask spans instead.
  • tracing.slowInteractions spans every interaction of 200ms or more (INP's "needs improvement" line) as interaction <event>. The name comes from the event whose handlers ran longest, so it's the click, not the pointerdown of the same interaction. Attributes: input delay, processing, presentation and the target selector.
  • Both use buffered PerformanceObservers in the deferred chunk, so jank from before the SDK finished loading is reported too. They nest under the open navigation span and follow tracing.sampleRate.
  • Upstream instrumentation-user-interaction isn't used: it needs zone.js for useful async context, and event timing already carries the breakdown.

The session sink's short selector helper is exported for the interaction target. Deferred budget 12 → 14 kB; eager is unchanged.

Testing

Browser tests:

  • a real slow click via userEvent becomes one interaction click span with the target and processing time;
  • the longtask fallback with LoAF support hidden (headless Chromium lists LoAF but renders no frames to report);
  • a synthetic LoAF entry for script attribution and URL redaction. Real buffered LoAF entries from earlier tests showed up too, which confirms the buffered path.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features
    • Added opt-in tracing for browser frames lasting at least 100 ms and interactions taking at least 200 ms. Spans include available timing and target details and can be nested under an active navigation span.
    • Added a public way to generate selectors for browser elements.
  • Documentation
    • Documented the tracing options, span details, browser fallbacks, and sampling behavior.
  • Chores
    • Increased the deferred bundle size budget from 12 kB to 14 kB.

- tracing.longFrames spans every frame of 100ms or more as
  longAnimationFrame, attributed to the script that ran longest
  (code.file.path, code.function.name, maple.browser.script.invoker and
  duration) plus the frame's blocking duration. Browsers without the Long
  Animation Frames API get longtask spans instead.
- tracing.slowInteractions spans every interaction of 200ms or more as
  interaction <event>, named after the event whose handlers ran longest
  (the click, not its pointerdown), with input delay, processing and
  presentation times and the target selector.
- Both use buffered PerformanceObservers in the deferred chunk, so jank
  from before the SDK finished loading is reported too, and nest under the
  open navigation span. They follow tracing.sampleRate.

The session sink's short selector helper is exported for the interaction
target. Deferred budget 12 -> 14 kB; eager is unchanged.
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 47 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 8ebba2e9-8321-4f79-a50b-05b53ac1b6c0

📥 Commits

Reviewing files that changed from the base of the PR and between a38cd6e and f8588b2.

📒 Files selected for processing (11)
  • docs/browser-sdk.md
  • packages/browser-session/src/capture/interactions.ts
  • packages/browser-session/src/index.ts
  • packages/browser/scripts/size.ts
  • packages/browser/src/config.ts
  • packages/browser/src/deferred/index.ts
  • packages/browser/src/deferred/perf.browser.test.ts
  • packages/browser/src/deferred/perf.ts
  • packages/browser/src/navigation.test.ts
  • packages/browser/src/navigation.ts
  • packages/browser/src/tracing.browser.test.ts
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@maple-review-bot

maple-review-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Maple review

Confidence 4/5 · likely safe to merge
scriptsOf reads LoAF script fields through Object.entries, and only a synthetic entry object covers the attribution path.
quality 98/100 · 1 note · tests covered · risk low · 2/2 new units observable

Adds opt-in tracing.longFrames and tracing.slowInteractions span sources to the browser SDK's deferred chunk, with browser tests for the interaction, longtask fallback and LoAF attribution paths. Contained and safe to merge apart from one misplaced config doc comment.

  • startPerf observes long animation frames and slow interactions, opt-in per session
  • longAnimationFrame spans carry the longest script's code.file.path and invoker
  • interaction <event> spans carry input delay, processing and presentation
  • selectorOf is exported from @maple/browser-session for the interaction target

Findings

Note · F1 · captureHeaders JSDoc block now documents longFrames

maintainability · packages/browser/src/config.ts:91-101

The captureHeaders doc block (config.ts:85-90) is left directly above the two new options, so in the published typings and editor tooltips longFrames is described as recording request/response headers while captureHeaders documents nothing. Move the new options below captureHeaders so each option keeps its own comment.

		readonly captureHeaders?: {
			readonly request?: ReadonlyArray<string>
			readonly response?: ReadonlyArray<string>
		}
		/**
		 * Span main-thread frames of 100ms or more (`longAnimationFrame`, with the
		 * script that ran longest; `longtask` where that API is missing). Default false.
		 */
		readonly longFrames?: boolean
		/** Span interactions of 200ms or more (`interaction click`, ...), split into input delay, processing and presentation. Default false. */
		readonly slowInteractions?: boolean
What was checked
  • New attributes are maple.browser.* or existing keys; live data already carries code.file.path (explore_attributes)
  • Sampling stays per-session through SessionSampler (sampling.ts:52-60); consent re-checked in ConsentSpanExporter
  • Both observers disconnect on the deferred stop (deferred/index.ts:43), and the fallback is guarded on supportedEntryTypes
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
long frame / long task observer (PerformanceObserver) background work yes onLongFrame ends a longAnimationFrame/longtask span per entry, parented to the open navigation (perf.ts:37-39, perf.ts:73-88)
slow interaction observer (PerformanceObserver) background work yes spanInteraction ends one interaction <event> span per interaction id (perf.ts:110-121, perf.ts:151-154)
Copy all findings (1)
Findings from an automated review of commit 46da4bed1bd0f67de813588ae3beda50439d11db. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Note · maintainability · packages/browser/src/config.ts:91-101
`captureHeaders` JSDoc block now documents `longFrames`
The `captureHeaders` doc block (`config.ts:85-90`) is left directly above the two new options, so in the published typings and editor tooltips `longFrames` is described as recording request/response headers while `captureHeaders` documents nothing. Move the new options below `captureHeaders` so each option keeps its own comment.
Replace those lines with:
		readonly captureHeaders?: {
			readonly request?: ReadonlyArray<string>
			readonly response?: ReadonlyArray<string>
		}
		/**
		 * Span main-thread frames of 100ms or more (`longAnimationFrame`, with the
		 * script that ran longest; `longtask` where that API is missing). Default false.
		 */
		readonly longFrames?: boolean
		/** Span interactions of 200ms or more (`interaction click`, ...), split into input delay, processing and presentation. Default false. */
		readonly slowInteractions?: boolean

46da4be · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple to ask about one.

@maple-review-bot

maple-review-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Note

A newer push replaced d1b90ee before its review finished. The latest commit is reviewed in a new comment.

@maple-review-bot

maple-review-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Maple review

Confidence 5/5 · safe to merge
Opt-in browser spans only, no auth or data path touched; the one thing to fix is the misplaced captureHeaders JSDoc still open from the earlier review.
quality 98/100 · 1 note · tests covered · risk low · 2/2 new units observable

Adds two opt-in jank span sources to the browser SDK: longFrames (LoAF entries, longtask fallback) and slowInteractions, both buffered observers nested under the open navigation span. Contained to the deferred chunk behind tracing.* flags; safe to merge.

  • startPerf observes long-animation-frame/longtask and event entries with buffered observers
  • tracing.longFrames and tracing.slowInteractions opt-in flags plumbed through resolveConfig
  • openNavigationSpan() exposes the in-flight navigation span as parent
  • selectorOf exported from @maple/browser-session for the interaction target

Still open from earlier reviews

What was checked
  • Spans go through Maple's provider sampler, so tracing.sampleRate applies (perf.ts:35, tracing.ts:184)
  • Buffered entries that began before a consent grant are dropped by ConsentSpanExporter (tracing.ts:78)
  • Script URL is redacted via scrubUrl, credential params replaced (url-privacy.ts:104)
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
Long frame span (longAnimationFrame / longtask) span yes perf.ts:73 starts a span on liveMapleTracer with script and blocking-duration attributes
Slow interaction span (interaction <event>) span yes perf.ts:111 starts a span with input delay, processing, presentation and target

66b8bb1 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple to ask about one.

@maple-review-bot

maple-review-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Maple review

Confidence 3/5 · needs attention
scriptsOf loses every script attribute on real LoAF entries while the synthetic-entry test keeps passing, so attribution needs a look before merge.
quality 88/100 · 1 warning · 1 note · tests partial · risk low · 2/2 new units observable

Adds opt-in long-frame and slow-interaction spans to the browser SDK's deferred chunk, plus the openNavigationSpan seam and selectorOf export they use. Contained and default-off, but the long-frame script attribution never reaches a real browser.

  • tracing.longFrames / tracing.slowInteractions resolve to booleans in resolveConfig
  • startPerf observes long frames and event timing in the deferred chunk
  • openNavigationSpan nests those spans under the open navigation span
  • selectorOf is exported from @maple/browser-session for the interaction target

Findings

Warning · F2 · scriptsOf reads script attributes with Object.entries, so they are always undefined

correctness · packages/browser/src/deferred/perf.ts:49

A real PerformanceScriptTiming keeps invoker, sourceURL and sourceFunctionName as IDL accessors on its prototype, so Object.entries(script) returns no own enumerable keys and text() returns undefined for all three: production longAnimationFrame spans ship without code.file.path, code.function.name and maple.browser.script.invoker, only duration (read directly) survives. The test passes a plain object literal, whose keys are own and enumerable, so it cannot catch this. Read the attribute off the object instead (Reflect.get(script, key) / (script as Record<string, unknown>)[key]).

		const text = (key: string): string | undefined => {
			const value = Reflect.get(script, key)
			return typeof value === "string" && value !== "" ? value : undefined
		}

Still open from earlier reviews

What was checked
  • Interaction dedup is bounded (seen.clear() past 500) and skips interactionId === 0 (perf.ts:145)
  • epoch() matches the at() epoch-ms convention already used in document-timing.ts:23
  • Both options default false in resolveConfig (config.ts:315), so existing installs are unchanged
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
Long-frame spans (longAnimationFrame / longtask fallback) with script attribution span yes Spans go through liveMapleTracer (perf.ts:35-39), so they export over OTLP like other browser spans
Slow-interaction spans (interaction <event>) under the open navigation span yes Same span() helper, attributes maple.browser.interaction.* (perf.ts:110-121)
Copy all findings (1)
Findings from an automated review of commit a9b49cbc4712f605cfbb534e6c04c4a5ae70fe56. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F2 · Warning · correctness · packages/browser/src/deferred/perf.ts:49
`scriptsOf` reads script attributes with `Object.entries`, so they are always undefined
A real `PerformanceScriptTiming` keeps `invoker`, `sourceURL` and `sourceFunctionName` as IDL accessors on its prototype, so `Object.entries(script)` returns no own enumerable keys and `text()` returns `undefined` for all three: production `longAnimationFrame` spans ship without `code.file.path`, `code.function.name` and `maple.browser.script.invoker`, only `duration` (read directly) survives. The test passes a plain object literal, whose keys are own and enumerable, so it cannot catch this. Read the attribute off the object instead (`Reflect.get(script, key)` / `(script as Record<string, unknown>)[key]`).
Replace those lines with:
		const text = (key: string): string | undefined => {
			const value = Reflect.get(script, key)
			return typeof value === "string" && value !== "" ? value : undefined
		}

a9b49cb · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple to ask about one.

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 inline note from Maple's review. The score and summary are in the review comment above.

Comment thread packages/browser/src/deferred/perf.ts Outdated
@Makisuo
Makisuo added this pull request to stack #1161 September 29, 2026 21:22
PerformanceScriptTiming keeps invoker, sourceURL and sourceFunctionName as
prototype getters, which Object.entries never sees, so real long frames
shipped without script attribution. Fields are read directly now, and the
test's script timings are getter-backed like the real ones. Also moves the
new tracing options below captureHeaders so each keeps its own doc comment.
@maple-review-bot

maple-review-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Maple review

Confidence 4/5 · likely safe to merge
The perf spans reuse the existing tracer, sampler and consent gate, and the browser tests exercise a real slow click and the LoAF fallback.
quality 100/100 · no findings · tests covered · risk medium · 2/2 new units observable

Adds two opt-in browser tracing sources: longAnimationFrame/longtask spans for frames ≥100ms (with script attribution) and interaction <event> spans for interactions ≥200ms, both buffered and nested under the open navigation span. Contained, tested, safe to merge.

  • startPerf observes long frames and slow interactions in the deferred chunk
  • tracing.longFrames / tracing.slowInteractions config options (default false)
  • openNavigationSpan exported so perf spans nest under the navigation
  • selectorOf exported from @maple/browser-session for the interaction target

Fixed since the last review

  • F1 · captureHeaders JSDoc block now documents longFrames
What was checked
  • Sampling: perf spans come from liveMapleTracer, so they follow the provider's SessionSampler (tracing.ts:184)
  • Consent: spans are only created under hasConsent() and the exporter drops pre-consent start times (tracing.ts:78)
  • Threshold filter makes split event batches safe: a sub-200ms first entry is skipped before seen is written (perf.ts:148)
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
long-frame spans (longAnimationFrame / longtask fallback) span yes packages/browser/src/deferred/perf.ts:75-90 starts the span with maple.browser.frame.blocking_duration_ms, code.file.path, code.function.name, maple.browser.script.*
slow-interaction spans (interaction <event>) span yes packages/browser/src/deferred/perf.ts:112-123 starts the span with input delay / processing / presentation / target attributes

f8588b2 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple to ask about one.

@Makisuo
Makisuo merged commit c7730f0 into main Sep 29, 2026
37 checks passed
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.

1 participant