From 950dcfea5b9921d14a273c909a44963bb7f2e1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20M=C4=99dryga=C5=82?= Date: Mon, 17 Aug 2026 16:12:52 +0200 Subject: [PATCH 1/2] fix(runner): inject the Tier-1 reporter on one line (DEV-2557) The module-entry injection prepended the whole reporter body to the entry file, so every position the bundler reported for it was shifted by the reporter's length -- 283 lines today, 226 at the releases the Sentry events were tagged with (both measured, not carried over). The visitor is shown those positions verbatim, which is how a syntax error in a 70-line file came back as "(257:22)". The reporter is now handed to the entry as a single physical line: a JSON-encoded indirect eval, computed once at module load. Measured live on the vue starter, a syntax error typed on authored line 11: reported line 316 before, 14 after. Note the residual +3 -- that entry is transpiled before the injection, so this does NOT make line numbers correct for any entry. It removes the distortion we add, nothing more, and the commit and comments deliberately claim only that. Not an arithmetic correction of the reported numbers, deliberately. The entries run transpileFilesForParcel before the injection and babel does not use `retainLines`, so a bundler position is already in compiled coordinates -- subtracting a constant would replace an obviously-absurd number with a plausible, confident, still-wrong one. Source maps are the way to close the rest, and this is a prerequisite for that rather than a competitor. A separate reporter module was the other candidate and was rejected: it would put a specifier the author never wrote into the graph next to a moving entry path, entangled with resolveSandboxEntry, sameFiles and stampEntry, and its failure mode is a blank preview. The eval form has no graph interaction. Its try/catch means a blocked eval costs monitoring on that path and never the demo. Verified live rather than inferred, since the try/catch would hide a failure: window.__hotRunnerMonitor is true inside the real Sandpack preview iframe for both a vue and a react entry. This needed the reporting gate forced open locally -- monitorDemos is host-pinned and webdriver-gated, so the obvious "build with VITE_MONITOR_DEMOS=1 and look" check silently proves nothing. The HTML path is untouched -- it is Tier-2's only monitoring channel and widening the eval bet to it wants its own decision. Byte-determinism, ES5 and idempotency all still hold and are now tested for the module entry too: the prefix is a pure function of a constant, carries no line terminator of any kind, and MONITOR_MESSAGE_TYPE survives the JSON escaping so a double injection stays a no-op. The acorn ES5 guard stays pointed at REPORTER_SOURCE, with a comment saying why -- under the new shape the injected output is one string literal that parses at ES5 forever, so repointing it there would check nothing. Co-Authored-By: Claude Opus 5 --- runner/packages/runtime/src/monitor.ts | 53 ++++++++- runner/pipeline/monitor-inject.test.mjs | 144 +++++++++++++++++++++--- 2 files changed, 182 insertions(+), 15 deletions(-) diff --git a/runner/packages/runtime/src/monitor.ts b/runner/packages/runtime/src/monitor.ts index 57b9d672..f0c0631f 100644 --- a/runner/packages/runtime/src/monitor.ts +++ b/runner/packages/runtime/src/monitor.ts @@ -581,6 +581,51 @@ export const REPORTER_SOURCE = `(function () { })(); `; +/** + * The reporter as a *single physical line*, for prepending to a JS module entry. + * + * DEV-2557. Whatever we prepend to the entry shifts every position the bundler + * reports for that file, and the visitor is shown those positions verbatim. Inlining + * the reporter body cost 226 lines at the releases the Sentry events were tagged + * with, and 283 after DEV-2552 grew it — which is how a syntax error in a 70-line + * file came back as "(257:22)". One line of prefix means one line of shift. + * + * Why an indirect eval and not a separate module the entry imports: a new module + * would put a specifier the author never wrote into the graph, next to a moving entry + * path, entangled with `resolveSandboxEntry`, `sameFiles` and `stampEntry`, and would + * depend on the classic bundler evaluating an injected dependency before the entry + * body. Its failure mode is a blank preview. This form has zero graph interaction. + * + * Why `(0,eval)` and not `eval`: the indirect form evaluates in global scope, where + * the reporter's bare `window`/`parent`/`document`/`location`/`XMLHttpRequest` + * resolve, and where it leaks no bindings into the bundler's module wrapper. + * + * Why the try/catch: if `eval` is ever unavailable, that must cost monitoring on this + * path and never the demo. An entry that resolves to an HTML file (`parcel`/`static` + * with an `htmlEntry` — see `resolveSandboxEntry`) keeps the `