Skip to content

Commit c8f1963

Browse files
authored
fix(canvas): decode unicode escapes in freeform sandbox JSX text
Generated canvases sometimes contain \uXXXX escapes in JSX text and attribute strings, which JSX renders verbatim. Decode them in a Babel pre-pass inside the sandbox so both new and already-saved canvases render the intended glyphs. Escapes in real JS string literals are left untouched. Generated-By: PostHog Code Task-Id: 20fa55b3-faa2-41b2-91a4-a6bab80ec174
1 parent cab5b24 commit c8f1963

1 file changed

Lines changed: 35 additions & 0 deletions

File tree

packages/ui/src/features/canvas/freeform/sandboxRuntime.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,40 @@ export function buildSandboxDocument(
269269
),
270270
);
271271
272+
// JSX text and attribute strings never process \\uXXXX escapes (they render
273+
// verbatim, e.g. "\\u00b7" instead of "·"), but generated canvases still
274+
// contain them despite the prompt rules — decode at transpile time so both
275+
// new and already-saved canvases render the real characters. Escapes inside
276+
// JS string/template literals are untouched (Babel already decoded those).
277+
const decodeUnicodeEscapes = (value) =>
278+
value.replace(
279+
/\\\\u\\{([0-9a-fA-F]{1,6})\\}|\\\\u([0-9a-fA-F]{4})/g,
280+
(match, braced, plain) => {
281+
try {
282+
return String.fromCodePoint(parseInt(braced || plain, 16));
283+
} catch {
284+
return match;
285+
}
286+
},
287+
);
288+
const jsxUnicodeEscapesPlugin = () => ({
289+
visitor: {
290+
JSXText(path) {
291+
path.node.value = decodeUnicodeEscapes(path.node.value);
292+
},
293+
JSXAttribute(path) {
294+
const v = path.node.value;
295+
if (v && v.type === "StringLiteral") {
296+
const decoded = decodeUnicodeEscapes(v.value);
297+
if (decoded !== v.value) {
298+
v.value = decoded;
299+
v.extra = undefined; // drop stale raw so the decoded value is emitted
300+
}
301+
}
302+
},
303+
},
304+
});
305+
272306
let root = null;
273307
// mount() is async and is called once per streamed code snapshot, so several
274308
// runs overlap on their awaits. Without ordering, a slower EARLIER (partial,
@@ -282,6 +316,7 @@ export function buildSandboxDocument(
282316
try {
283317
const out = Babel.transform(code, {
284318
filename: "canvas.tsx",
319+
plugins: [jsxUnicodeEscapesPlugin],
285320
presets: [
286321
["react", { runtime: "automatic" }],
287322
["typescript", { isTSX: true, allExtensions: true, onlyRemoveTypeImports: true }],

0 commit comments

Comments
 (0)