fix(canvas): decode unicode escapes in freeform sandbox JSX text#3214
Conversation
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
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
|
Reviews (1): Last reviewed commit: "fix(canvas): decode unicode escapes in f..." | Re-trigger Greptile |
| const decodeUnicodeEscapes = (value) => | ||
| value.replace( | ||
| /\\\\u\\{([0-9a-fA-F]{1,6})\\}|\\\\u([0-9a-fA-F]{4})/g, | ||
| (match, braced, plain) => { | ||
| try { | ||
| return String.fromCodePoint(parseInt(braced || plain, 16)); | ||
| } catch { | ||
| return match; | ||
| } | ||
| }, | ||
| ); | ||
| const jsxUnicodeEscapesPlugin = () => ({ | ||
| visitor: { | ||
| JSXText(path) { | ||
| path.node.value = decodeUnicodeEscapes(path.node.value); | ||
| }, | ||
| JSXAttribute(path) { | ||
| const v = path.node.value; | ||
| if (v && v.type === "StringLiteral") { | ||
| const decoded = decodeUnicodeEscapes(v.value); | ||
| if (decoded !== v.value) { | ||
| v.value = decoded; | ||
| v.extra = undefined; // drop stale raw so the decoded value is emitted | ||
| } | ||
| } | ||
| }, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Missing tests for the new decode logic
The decodeUnicodeEscapes function and jsxUnicodeEscapesPlugin contain non-trivial regex logic with several distinct cases (4-hex \uXXXX, braced \u{...}, surrogate pairs, invalid code points that should pass through unchanged, escapes already processed by Babel that should be left alone). There are no new automated tests covering any of these paths. The custom instruction says parameterised tests are always preferred — a table-driven test over representative inputs would guard against regressions here.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Lift the decoder out of the bootstrap string as an exported function (interpolated back in via toString) so it can be unit-tested, add parameterised tests over the decode cases, and only write JSXText values back when they actually changed. Addresses Greptile review. Generated-By: PostHog Code Task-Id: 20fa55b3-faa2-41b2-91a4-a6bab80ec174
|
Reviews (2): Last reviewed commit: "test(canvas): cover unicode-escape decod..." | Re-trigger Greptile |
Problem
Freeform canvases render literal
\uXXXXescape sequences in visible text (e.g. "Survey started Jun 20, 2026 · live") instead of the decoded characters. Generated canvas source sometimes contains these escapes in JSX text, and JSX doesn't process them — so they show up verbatim, including in canvases that were already saved with escapes baked in.Changes
Added a small Babel pre-pass to the sandbox bootstrap in
sandboxRuntime.tsthat decodes\uXXXX/\u{...}escapes in JSX text nodes and JSX attribute string values before the JSX transform runs. Escapes inside real JS string/template literals are untouched (Babel already decodes those). Because it runs at transpile time in the iframe, it fixes both newly generated and previously saved canvases — no migration needed.How did you test this?
@babel/standalone@7.26.4(the pinned sandbox version) against a repro including the reported string —\u00b7decodes to·, attribute values decode, and\u{...}code points / surrogate pairs work; escapes in JS string literals and invalid sequences are left intact.pnpm --filter @posthog/ui typecheck, biome lint, and all 1232@posthog/uiunit tests pass.Automatic notifications
Created with PostHog Code