From 201a79f9ee31ca177f130196f5e2f8cf18b08e78 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Thu, 6 Aug 2026 13:25:17 -0700 Subject: [PATCH] fix(core): escape `<` in the compiler-emitted variables script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `` terminated the element early and the remainder was parsed as markup, corrupting the compiled document. Rewrite `<` to its JSON unicode escape. This is transparent to JSON.parse, so the table the runtime reads is unchanged. Co-Authored-By: Claude Fable 5 --- .../src/compiler/compositionScoping.test.ts | 58 +++++++++++++++++++ .../core/src/compiler/compositionScoping.ts | 11 +++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/core/src/compiler/compositionScoping.test.ts b/packages/core/src/compiler/compositionScoping.test.ts index 7985c9f809..75cde782ba 100644 --- a/packages/core/src/compiler/compositionScoping.test.ts +++ b/packages/core/src/compiler/compositionScoping.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import { parseHTML } from "linkedom"; import { + buildVariablesByCompScript, scopeCssToComposition, wrapInlineScriptWithErrorBoundary, wrapScopedCompositionScript, @@ -886,3 +887,60 @@ window.__timelines['intro'] = tl; expect(gsapTargets).toEqual([["HELLO"]]); }); }); + +/** + * The emitted statement is placed inside a ` b c" } }; + const body = buildVariablesByCompScript(variables) ?? ""; + const fakeWindow: Record = {}; + new Function("window", body)(fakeWindow); + expect(fakeWindow.__hfVariablesByComp).toEqual(variables); + }); + + it("returns null when there are no per-instance values", () => { + expect(buildVariablesByCompScript({})).toBeNull(); + }); +}); diff --git a/packages/core/src/compiler/compositionScoping.ts b/packages/core/src/compiler/compositionScoping.ts index cbe75b0f87..2566660b66 100644 --- a/packages/core/src/compiler/compositionScoping.ts +++ b/packages/core/src/compiler/compositionScoping.ts @@ -601,10 +601,19 @@ export function wrapInlineScriptWithErrorBoundary(source: string, errorLabel: st * `getVariables()` returned `{}` only during render — parametrized sub-comps * silently shipped blank/default text in the final MP4 while snapshot QA passed * (issue #2064). Both callers now share this one builder so they can't drift. + * + * Every `<` is rewritten to its JSON unicode escape because this body is + * emitted into a `` would otherwise close the + * element early and the remainder would parse as markup. The escape is + * transparent to `JSON.parse`, so the value the runtime reads is unchanged. */ export function buildVariablesByCompScript( variablesByComp: Record>, ): string | null { if (!variablesByComp || Object.keys(variablesByComp).length === 0) return null; - return `window.__hfVariablesByComp = Object.assign({}, window.__hfVariablesByComp || {}, ${JSON.stringify(variablesByComp)});`; + const json = JSON.stringify(variablesByComp).replace(/