Corrective Action: Unicode Escape Double-Encoding in Vite/esbuild JSX Output

Corrective Action: Unicode Escape Double-Encoding in Vite/esbuild JSX Output

Date: March 16, 2026 Category: Tool Limitation Impact: 49 unicode characters rendered as literal escape text (\u25CF instead of ) in production; user-facing visual defect across entire RPG walkthrough microsite Resolution Time: Same session — sed replacement of all 49 occurrences, rebuild, redeploy


Incident

What Happened

The builder agent created /mnt/d/Projects/value-first-operations/clients/roompricegenie/apps/rpg-walkthrough/src/RPGWalkthrough.jsx using JavaScript unicode escape sequences ("\u25CF", "\u2192", "\u2014") in JSX string literals and props. After the Vite build, these escape sequences were double-escaped in the output bundle — \u25CF became \\u25CF — causing all 49 unicode characters to render as literal backslash-u text instead of the intended glyphs (bullet , arrow , em dash ). Chris caught it in production.

Timeline

Time Event
Build Agent generated RPGWalkthrough.jsx with 49 \uXXXX escape sequences in JSX string literals
Deploy Vite/esbuild build completed without errors — no build-time indication of the problem
Production All 49 characters rendered as literal text: \u25CF instead of , \u2192 instead of , \u2014 instead of
Chris review Caught the visual defect in production
Fix sed replacement of all escape sequences with actual Unicode characters; rebuild; redeploy confirmed clean

Root Cause

The agent wrote JavaScript unicode escape sequences (\u25CF) as string values in JSX props and template literals. Example:

// What the agent wrote (WRONG)
icon="\u25CF"
// What the agent should have written (CORRECT)
icon="●"

When Vite/esbuild processes JSX source into the production bundle, it serializes string literals. The \u25CF in source is a valid JavaScript escape representing the character . However, during the esbuild transform and bundle output, the backslash is escaped again, producing the string \\u25CF in the final JavaScript. The browser then renders the literal characters \u25CF instead of interpreting them as a unicode escape.

Category: Tool Limitation

This is the JSX equivalent of the existing esbuild lesson about em dashes and */ in block comments. The esbuild/Vite bundler chain does not preserve unicode escape semantics through its transform pipeline the way a direct JavaScript interpreter would. The escape sequence is valid JavaScript, but the build tool's string serialization re-escapes the backslash.

The agent's training data includes \uXXXX escape sequences as a common, valid pattern for representing unicode characters in JavaScript. This is technically correct in raw JS, but incorrect for source files processed by Vite/esbuild where the build pipeline's string handling introduces a double-escape.

Why This Will Recur

  1. Training data prevalence. Unicode escape sequences are ubiquitous in training data. They appear in documentation, examples, and codebases as the "safe" way to include non-ASCII characters.
  2. No build-time error. The double-encoding produces valid JavaScript — the string just contains the wrong characters. There is no compiler warning, no type error, no build failure.
  3. Visual-only symptom. The defect is only visible when a human looks at the rendered output. Automated build checks pass clean.

Fix Applied

Immediate Resolution

sed replacement of all 49 unicode escape sequences with their actual Unicode character equivalents in RPGWalkthrough.jsx. The three affected character classes:

Escape Character Usage
\u25CF Bullet/dot icons
\u2192 Arrow indicators
\u2014 Em dashes in text

Code/Configuration Changes

File Change
clients/roompricegenie/apps/rpg-walkthrough/src/RPGWalkthrough.jsx Replaced all 49 \uXXXX sequences with actual Unicode characters

Verification

Rebuild via npm run build completed clean. Grep of dist/ output for \\u patterns returned zero matches. Redeployed to production — all characters render correctly.


Prevention Measures

Rules Added

Layer File Rule
Critical Lessons memory/MEMORY.md "NEVER use \uXXXX unicode escape sequences in JSX/React source files processed by Vite/esbuild. Use actual Unicode characters directly ( not \u25CF). The build pipeline double-escapes the backslash, rendering literal text. Fixed Mar 16 after 49 occurrences in RPG walkthrough shipped to production."
Self-Correction skills/enforcement/vf-self-correction.md Infrastructure trigger: writing \uXXXX patterns in .jsx/.tsx files

Detection Triggers

Code Generation Trigger: If an agent is writing .jsx or .tsx files and includes any \uXXXX escape sequence in a string literal, JSX prop, or template literal — STOP. Replace with the actual Unicode character. The Vite/esbuild pipeline will double-escape the backslash.

Build Verification Trigger: After building any Vite/React microsite, grep the dist/ output for \\u patterns:

grep -r '\\\\u[0-9A-Fa-f]\{4\}' dist/

If matches are found, the source contains escape sequences that were double-encoded.

Recommended Workflow Additions

  1. Microsite build verification step. After npm run build for any microsite, run the \\u grep on dist/ output. Zero matches = clean. This catches the problem before deploy.

  2. Scoping-microsite template guidance. Add a warning to the microsite creation workflow README: "Use actual Unicode characters in JSX source, never \uXXXX escape sequences. Vite/esbuild double-escapes them."

  3. Generalized rule for all Vite projects. This applies to every React/JSX/TSX file in any Vite-built project — not just microsites. The portal (apps/portal/) and any future Vite apps are equally affected.


Lessons

This is the same class of problem as the esbuild em-dash-in-block-comments bug (MEMORY.md, fixed Mar 4): the build tool's string handling does not preserve source-level character encoding semantics. The generalized principle is that esbuild/Vite source files should contain only literal characters, never escape sequences that depend on runtime interpretation. If you would not want to see the escaped form in production output, do not put the escaped form in source.

The absence of build-time errors makes this particularly dangerous. The build succeeds. The types check. The only signal is a human looking at the rendered output and seeing \u25CF instead of . Automated detection (grepping dist output) is the correct prevention — it converts a visual-only symptom into a gatable check.


Related Incidents

  • esbuild em dash / */ in block comments (MEMORY.md, Mar 4, 2026): Same tool limitation class. esbuild cannot handle em dashes or */ inside block comments in .ts files run via npx tsx. Solution: use line comments. Same principle — avoid source constructs that the build tool mishandles.
  • Portal listing_content_type exact match (MEMORY.md, Mar 2, 2026): Different mechanism, same pattern — a value that looks correct in source but produces wrong results downstream due to toolchain behavior. The fix in both cases is verification at the output layer, not trust at the input layer.