Conversation
Several investigations into extension-loading/RPC-timing bugs (SITES-48958,
SITES-49454, and the Universal Editor extension-loading-timeout work) each
independently hand-rolled the same throwaway tracing: an unconditional
console.log, gated by a flag re-checked or re-parsed per call, formatted by
hand at every call site. That last part caused the same bug three times --
passing an object as a second console.log argument collapses to unreadable
`Array(N)`/`[{…}]` placeholders in a plain-text devtools export, so every
investigation had to rediscover and fix that convention for itself.
createTracer(namespace) centralizes this:
- enabled is a live getter (cheap property read, no re-parsing), so a
disabled tracer costs one function call + one property read + one typeof
check -- no string building, no object allocation, no JSON.stringify.
- details may be a function; it's only invoked while the namespace is
enabled, so building it (mapping a guest list, serializing an error) never
happens on the hot disabled path.
- Formatting (inline JSON.stringify into the log message, with a timestamp)
is owned by the tracer itself, not the call site -- the collapsing-object
mistake becomes structurally impossible instead of a convention to
remember.
window.__UIX_DEBUG__ exposes enable(pattern)/disable()/list() at runtime --
query param (?uixDebug=...) or localStorage ("uix:debug") for persistence
across reloads, exact/wildcard/"*" pattern matching, live against whichever
tracers exist at call time (so enable("*") also covers tracers created
afterward, regardless of module load order).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… channels
Wires createTracer into the three spots that took a multi-hour vendored-tarball
detour to instrument ad-hoc in the last two investigations:
- Host.addLoadsNewGuests/loadOneGuest (uix-host namespace): batch start/settle
with the guest-id list, and per-guest connect start/succeeded/failed with
duration -- answers "which guest is slow or broken, and how long does the
whole batch take."
- Port.invokeHostMethod (uix-host namespace): logs every call along with
which host-api namespaces are currently registered for that guest, and
whether it succeeded or threw (including the async case, via a
non-blocking .then/.catch purely for tracing -- behavior is unchanged).
This is what previously surfaced "host. has no property X" errors caused
by Api.ts's updateOn: "all" batch-gating bug.
- Guest.invokeChecker (uix-guest namespace): the unbounded 500ms retry loop
wrapping every guest->host call, with a per-call id and attempt counter --
answers "how many times did this retry, and for how long."
None of this runs unless a consuming app (or someone in devtools) calls
__UIX_DEBUG__.enable("uix-host,uix-guest"). No behavior change when disabled.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds basic coverage that the wired-in tracing actually behaves as designed, at each of the three call sites from the previous commit: - host.test.ts (new -- no host.test.ts existed on main before this): Host.addLoadsNewGuests/loadOneGuest log batch start/settle and per-guest connect outcomes once uix-host is enabled, and log nothing otherwise (mocks Port entirely; real connection behavior is covered by port.test.ts). - port.test.ts: Port.invokeHostMethod logs the call plus which host-api namespaces are currently registered, and logs a throw when the requested namespace hasn't been provided -- the exact signature that surfaced the Api.ts updateOn: "all" bug in the extension-loading investigation. - guest.test.ts (new -- uix-guest had no wired-up jest project on main; added one alongside core/host/host-react): Guest.invokeChecker logs each attempt/success/failure with an incrementing attempt count and a callId held stable across the whole retry chain, and logs nothing while disabled. Every suite asserts the disabled case emits zero console.log calls -- the core "no behavior/perf impact when off" claim, verified rather than just documented. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Three separate investigations this cycle (SITES-48958, the Universal Editor extension-loading-timeout work, and SITES-49454/RTE load) each hand-rolled the same throwaway instrumentation to see what
Host.load()/Port.invokeHostMethod/Guest.invokeCheckerwere actually doing at runtime — an unconditionalconsole.log, a flag re-checked per call, formatting done by hand at every call site. The formatting mistake (passing an object as a secondconsole.logargument, which collapses to unreadableArray(N)/[{…}]placeholders in a plain-text devtools export) got made and re-fixed three separate times because there was nowhere for the lesson to live.Each of those investigations also required vendoring a patched tarball into the consuming app just to get visibility — a multi-hour detour purely to answer "which guest is slow, and how long did it take."
What this adds
createTracer(namespace)in@adobe/uix-core(packages/uix-core/src/trace.ts):tracer.enabledis a live getter — a disabled tracer costs one function call + one property read + onetypeofcheck. No string building, no object allocation, noJSON.stringify, ever, while disabled.detailsmay be a plain object or a function returning one. Pass a function whenever building it costs more than a property read (mapping a guest list, serializing an error) — it's only invoked while the namespace is enabled.JSON.stringify, single log line, ISO timestamp) — a call site can no longer reintroduce the collapsing-object bug, because it never touchesconsole.logdirectly.window.__UIX_DEBUG__—enable(pattern)/disable()/list()— lets anyone with devtools access turn tracing on after the fact, at runtime, without rebuilding or redeploying anything:"uix-host"), trailing wildcard ("uix-*"), or"*"for everything.?uixDebug=...query param orlocalStorage["uix:debug"], so it survives a reload.enable("*")(or any pattern) also covers tracers created afterward — no dependency on module load order.Wired into the three spots that needed ad-hoc vendoring to see previously:
Host.addLoadsNewGuests/loadOneGuest(uix-hostnamespace): batch start/settle with the guest-id list, per-guest connect timing and outcome.Port.invokeHostMethod(uix-hostnamespace): every call, which host-api namespaces are currently registered for that guest, success/throw (including the async case via a non-blocking.then/.catch— behavior unchanged).Guest.invokeChecker(uix-guestnamespace): the unbounded 500ms retry loop wrapping every guest→host call, with a per-call id and attempt counter.Testing
packages/uix-core/src/trace.test.ts: namespace matching (exact/wildcard/*), disabled-by-default, details-thunk never called while disabled,enable()/disable()/list(),localStoragepersistence across a simulated reload, retroactive enable of tracers created afterenable("*").packages/uix-host/src/host.test.ts(nohost.test.tsexisted onmainbefore this):addLoadsNewGuests/loadOneGuestlog batch start/settle and per-guest connect outcomes onceuix-hostis enabled, and log nothing otherwise (mocksPortentirely — real connection behavior stays covered by the existingport.test.ts).packages/uix-host/src/port.test.ts: newinvokeHostMethod tracingblock — logs the call plus currently-registered host-api namespaces, logs a throw when the requested namespace hasn't been provided yet (the exact signature that surfaced theApi.tsupdateOn: "all"bug in the extension-loading investigation), and logs nothing while disabled.packages/uix-guest/src/guest.test.ts, plus auix-guestjest project injest.config.ts(uix-guest had no wired-up unit tests onmainat all before this):invokeCheckerlogs each attempt/success/failure with an incrementing attempt count and acallIdheld stable across the whole retry chain, and logs nothing while disabled.console.logcalls — the "no behavior/perf impact when off" claim is verified, not just documented.npx tsc --buildclean across the whole monorepo.npx jest --selectProjects uix-core uix-host uix-guest— 65 passing, 0 failing, 13/13 suites.npx tsupbuildsuix-core,uix-host, anduix-guestclean; verified the tracing calls are present in each builtdist/index.js.prettier --checkclean on all touched files.No behavior change when tracing is disabled (the default).
🤖 Generated with Claude Code