The Host Isolation Contract

A third-party script gets to be on your page once. The performance budget our widget is held to, the governor that degrades it, and the build gate behind both.

Four glass walls enclose a dark core, with one orange governor bar set into the front

Every third-party script promises it is lightweight. The promise is worth nothing on its own, because the thing that hurts your page is rarely the number in the marketing copy. It is the fourth chunk that loads on scroll, or the observer that fires during your own animation, or the retry loop that keeps going while the tab is hidden.

So the limits are written down as ceilings and a build gate stands behind them. An artifact over budget exits the build non-zero and never ships. That is the only version of this claim that means anything.

What the budget actually is

Fifteen gzipped ceilings are declared in the widget’s build script, and the build measures every artifact against its own. These are the ones a host page can feel:

ArtifactCeiling (gzip)
Widget loader (widget.js)3.5 KiB
Loader plus runtime core14.25 KiB
Interface, on user intent35.75 KiB
Any one complete load path64 KiB
Composer and review in one session75 KiB
Isolation Worker, lazy8 KiB

The loader measured 3.3 KiB gzipped on the build that produced this page, against a ceiling of 3.5. The interface measured 35.4 against 35.75. Both are close to their ceilings on purpose: headroom that is never spent is a budget nobody is actually held to, and the margin is what the next feature has to argue for.

The 64 KiB row is the interesting one. Annotation, screen capture, element selection, review mode, surveys and replay each get that same ceiling independently, so no single capability can quietly become the expensive one while the total still looks reasonable. There is one exception, and it is named: a page running the composer and review mode in the same session reaches 74.25 KiB, because that graph is the two smaller graphs reached by one person in one visit rather than new weight.

What loads, and when

The loader is the only thing that arrives with your page. It is unversioned, inert, and it does four things: install the public API stub, respect data-autostart="false", qualify startup health, and select a content-hashed runtime.

Everything after that is fetched from intent. The interface loads when somebody presses the launcher or calls open(). Element selection loads after verified reviewer intent. Screen capture is its own lazy chunk, kept out of the interface because it never loads on iOS at all and the interface is already near its ceiling. The isolation Worker loads lazily or not at all.

Nothing loads on idle. Nothing loads on hover. Nothing preloads in case you might want it.

What happens when your page is already busy

Budgets only describe the download. The other half is the runtime, and a script that behaves well on an empty test page can still be the thing that pushes a real one over.

The governor watches two different things. The widget’s own cost is charged against per-axis envelopes for CPU, memory, network and render. The host’s condition is read from signals the widget does not own: event loop drift and long tasks it did not cause. Then it steps down through three states.

Healthy is everything you configured, inside every envelope.

Constrained pauses session replay for the rest of the document and drops the cheap signal: informational logs, ordinary clicks, successful responses. It keeps what you would actually miss, which is uncaught errors, navigation, console warnings and network failures.

Protected takes the global hooks off your page altogether. What survives is minimal error capture, the public API, and a person’s ability to write feedback.

The triggers are specific rather than a general sense of pressure, and they are worth reading as a table because the asymmetry between them is the design.

MoveWhat causes itWhat undoes it
Healthy to ConstrainedAn envelope at 80 percent, an ambient long task, or two event loop probes drifting by 100 ms inside ten seconds5 seconds below 80 percent
Constrained to ProtectedA hard ceiling, one attributable task of 50 ms, one probe drifting by 500 ms, or three severe ambient signals in ten seconds30 seconds below half of every envelope

Why recovery is slower than escalation

Stepping down happens the moment a threshold is crossed. Stepping back up takes sustained quiet, and the right-hand column of the table above is where those two figures live.

Those two numbers are asymmetric for different reasons. Protected is the state a breach or a genuinely struggling host put the runtime in, so leaving it deserves the longer look. Constrained recovers on the same figure that caused it, and that is a correction: it used to demand 15 seconds below 50 percent, a hysteresis gap nothing documented, and with the scheduler’s own slices counted as pressure it kept replay withdrawn for roughly 20 seconds after every checkpoint. Constrained became the widget’s usual state rather than its exceptional one.

That is the failure mode worth naming. A degradation ladder built without a way back is not protecting the host page. It is turning itself off permanently on the first busy second and calling that safety.

Why this is the feature

Feedback tooling has a specific failure mode. It is installed once, by someone who cares about it, and then it lives on a page owned by someone who does not. The day it costs that person a performance regression, it comes off the page and never goes back.

Being unnoticeable is not a nice-to-have for this category. It is the whole job, and it is why the ceilings are in the build rather than in a promise. What the reader installs on the page those ceilings protect is one script tag.

Common questions

How much does the Notarus widget add to a page that never opens it?

3.3 KiB gzipped, once. That is the loader, and it is inert: it installs a bounded public API stub, validates the configuration and selects a runtime. A visitor who never presses the launcher downloads nothing else for the rest of the session, because every other chunk is fetched from user intent rather than on load.

What stops the widget from growing over time?

The build fails. Fifteen gzipped ceilings are declared in the widget's build script, and an artifact over its ceiling exits the build non-zero, so the bundle cannot ship. Raising a ceiling is a deliberate edit to that file with the reason written next to it, which is what makes the number a contract rather than a measurement.

What does the widget stop doing when the host page is under load?

It steps down through three pressure states. Constrained pauses session replay for the document and drops informational logs, ordinary clicks and successful network responses, keeping uncaught errors, navigation, console warnings and network failures. Protected removes the global hooks entirely, leaving minimal error capture, the public API and a person's ability to write feedback.

How does the widget know the page is under load rather than itself?

It measures both. Its own cost is charged against per-axis envelopes for CPU, memory, network and render. The host's condition is read from ambient signals it does not own: event loop drift and long tasks. An attributable task of 50 ms is treated differently from a page that is merely busy around it.

Why does the widget recover slowly after it degrades?

Escalation is immediate and recovery is deliberate, because a widget that recovered eagerly would spend a struggling page's budget oscillating between states. Leaving Protected takes 30 seconds below half of every envelope. Leaving Constrained takes 5 seconds below 80 percent, which is the same figure that caused it.

Does session replay run inside the same budget?

Yes, and it is the first capability withdrawn. Replay carries its own ceilings on top of the shared ones, and it runs only in the Healthy state. The moment any envelope reaches 80 percent, replay pauses for the rest of the document rather than competing with the page it is recording.

The Notarus team

The people who build Notarus. We write up the decisions behind the product when the reasoning is worth more than the changelog line.

Keep reading

Reports with the evidence attached.

Start free, one script tag.

<script src="…/widget.js" data-key="pb_pub_…">