---
layout: post
title: "Web-Perf Wednesday 011 – SpeedCurve’s INP Observer Missed Its Threshold"
date: 2026-09-30 12:00:00 +0100
categories:
- Web Performance
tags:
- RUM
- INP
- Tooling
show_taxonomy: true
main: ""
meta: "A long-standing lux.js bug left Event Timing on its 104 ms default, while browser releases improve testing and rendering performance."
---
This week has been quieter than [last
Wednesday](/2026/09/web-perf-wednesday-010-safari-keeps-scrolled-content-in-place/),
but the lead is exactly the sort of measurement bug worth stopping for. I found
that SpeedCurve’s public lux.js code wasn’t passing all of its
`PerformanceObserver` options to the browser correctly, which left Event Timing
using its default 104 ms threshold. The fix was merged quickly and now appears
in an open-source release. Safari Technology Preview and Firefox also shipped
smaller changes worth testing. Together, they’re a reminder to test the
measurement system as carefully as the site.
## SpeedCurve’s INP Observer Missed Its Threshold
While investigating an odd distribution in SpeedCurve RUM data, I found and
[reported a bug in
lux.js](https://github.com/SpeedCurve-Metrics/lux.js/issues/85). The library has
a small wrapper around `PerformanceObserver` that accepts an entry type, a
callback, and any extra observer options. The wrapper was putting those extra
options inside a nested `options` property instead of passing them at the top
level of the object sent to the browser.
That sounds like a minor object-shape error, but it changes what the browser
records. lux.js asks for Event Timing entries with `durationThreshold: 0`; the
browser never received that property where it expected it, so it fell back to
the [`PerformanceObserver.observe()` default of 104
ms](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver/observe).
Interactions below that threshold weren’t delivered through the ordinary
Event Timing observer, which could change the INP value and attribution that
SpeedCurve calculated for a page view.
The faulty option forwarding had been there for some time. The wrapper began
with the correct spread syntax in March 2023, then [an unrelated change in May
2023](https://github.com/SpeedCurve-Metrics/lux.js/commit/b5adb3e966cf699df5dc2a92c2422cc54be5dcfc)
introduced the nested object. lux.js began explicitly requesting a zero
threshold for INP in May 2024, but the wrapper still prevented that value from
reaching the browser. By the time I reported it, the bad forwarding code had
been present for more than three years.
SpeedCurve’s Joseph Wynn opened a patch a little over an hour after the report,
and [the fix was merged on 24
September](https://github.com/SpeedCurve-Metrics/lux.js/pull/86). It removes the
extra object wrapper so that `durationThreshold`, and any future
`PerformanceObserverInit` option, is passed through at the correct level. The
patch also adds a browser integration test that checks interactions below 104
ms, which is the right place to catch a Web API wiring problem like this.
For SpeedCurve users, the immediate task is to preserve context. Record the
lux.js version alongside the relevant RUM period, annotate the eventual rollout,
and compare the shape and attribution of INP before and after it. You can see
how I’m recording the lux.js version in [this recent
commit](https://github.com/csswizardry/csswizardry.github.com/commit/8181b4d7da105702c8924e13379c93b84edc3341).
A change in the distribution may come from the observer seeing interactions that
were previously absent rather than from an application deployment. A careful
[RUM measurement review](/consultancy/) should establish what the collector
could see before anyone uses the graph to judge a release, set a target, or
explain a commercial result.
## lux.js 4.5.3 Contains the Fix
[lux.js 4.5.3](https://github.com/SpeedCurve-Metrics/lux.js/releases/tag/v4.5.3)
was published early on 30 September, and its tag contains the merged fix. The
public release note describes an INP correction that mostly affected soft
navigations. That establishes the fix in a published open-source version rather
than only on the repository’s main branch.
SpeedCurve’s [public product
changelog](https://support.speedcurve.com/changelog) hadn’t officially
announced a `4.5.3` rollout at the time of writing, but lists the
4.5.3 release as having addressed the INP bug, and I have
successfully observed `4.5.3` being served to my and my client’s audiences, so
it looks like it’s on its way out. If you’re planning a before-and-after
comparison, confirm the deployed version first and keep the rollout time with
the data.
## Safari Adds Network Throttling and Performance Fixes
[Safari Technology Preview
253](https://webkit.org/blog/18357/release-notes-for-safari-technology-preview-253/)
adds network throttling to Web Inspector and fixes several browser-side
performance problems. WebKit lists excessive CPU and power use from pages with
many `IntersectionObserver` targets, multi-second main-thread blocking around
shared `scroll-timeline` names, redundant parsing of very long URLs, and
unnecessary full-layer repaints among the resolved issues.
These fixes are in Technology Preview, so they don’t describe current stable
Safari for every user. They do give teams useful test cases: repeat an expensive
Safari journey in 253, use the new network controls to make the conditions
reproducible, and check whether the application or the browser owns the work. A
[journey-level performance test](/performance-audits/) is far more useful when
it records both sides of that comparison.
## Firefox Cuts WebGPU Attachment Overhead
[Firefox 157](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/157)
shipped on 29 September with support for WebGPU’s `TRANSIENT_ATTACHMENT` texture
usage. It allows render-pass attachments to remain in tile memory, avoiding
VRAM traffic and potentially avoiding a VRAM allocation for textures whose
contents aren’t needed afterwards.
This is a specialised improvement, but it can be useful for graphics-heavy
applications on tile-based GPUs. Teams using WebGPU can now test the transient
path in stable Firefox, compare memory and frame behaviour on representative
hardware, and keep the existing path until those results justify a change. The
release note says the flag _can_ avoid traffic and allocation, so measure the
effect on the actual workload rather than treating it as a guaranteed saving.
## Need Help Checking Your SpeedCurve Data?
Once the fixed collector goes live for customers, I’d be very happy to provide
a second pair of eyes on an existing SpeedCurve instance. You might want to
check whether an INP distribution moved, whether attribution now points
somewhere different, or simply whether the data still supports the conclusion
the team had already reached.
This invitation is open to any SpeedCurve user, whether we already work
together or you’re completely new to me. You don’t need a finished diagnosis;
a dashboard, an odd pattern, or a loose question is plenty to begin with. If
you’d like me to take a look once the fix reaches the customer SDK, [get in
touch](/contact/).
{% include web-perf-wednesdays.md %}