Fix: never inject raw JSX in live-mode fallback (#454) (#694)

* Fix: never inject raw JSX in live-mode fallback (#454)

On React/JSX targets, missed HMR used to fetch source and DOMParser-inject it, painting {expressions} and comment markers as page text. Adopt a live wrapper that already has variants, otherwise leave HMR alone.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: wait for unmounted JSX variants instead of tearing down (#454)

A missing live wrapper on React is often a closed modal or other route, not a failed generation. Leave the observer armed so mount can still reach CYCLING.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: recover empty JSX replace wraps after fallback retries (#454)

Insert scaffolds still wait for HMR. A replace wrapper with no variants after retries is a failed generation and should leave GENERATING.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: align live-reference setup assertions with current SKILL.src.md

#689 shortened Setup step 2, but the live-reference tests still expected the old playbook sentence, which kept CI red on main and this branch.

AI assistance: Cursor Grok 4.6 implemented this change.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Abdul Wahab
2026-09-01 15:01:46 -07:00
committed by GitHub
co-authored by Cursor
parent a70acd2823
commit 38e102f0b2
2 changed files with 171 additions and 144 deletions
+41 -21
View File
@@ -553,37 +553,57 @@ describe('live-browser source contracts', () => {
);
});
it('normalizes generated JSX source before source-fallback DOM parsing', () => {
assert.match(
SOURCE,
/parser\.parseFromString\(normalizeSourceFallbackBlock\(block, filePath\), 'text\/html'\)/,
'source fallback should normalize JSX wrapper syntax before DOMParser sees it',
it('never DOMParser-injects JSX source (#454)', () => {
const isJsxStart = SOURCE.indexOf('function isJsxSourceFile(');
const isJsxEnd = SOURCE.indexOf('function completeSourceInjection', isJsxStart);
const isJsxSourceFile = new Function(
SOURCE.slice(isJsxStart, isJsxEnd) + '\nreturn isJsxSourceFile;',
)();
assert.equal(isJsxSourceFile('src/App.jsx'), true);
assert.equal(isJsxSourceFile('panel/src/Widget.tsx'), true);
assert.equal(isJsxSourceFile('index.html'), false);
assert.equal(isJsxSourceFile('Card.vue'), false);
const injectStart = SOURCE.indexOf('function injectVariantsFromSource(');
const injectEnd = SOURCE.indexOf('function buildSvelteExpressionTextMap', injectStart);
const injectFn = SOURCE.slice(injectStart, injectEnd);
const jsxGateIdx = injectFn.indexOf('if (isJsxSourceFile(filePath))');
const htmlFetchIdx = injectFn.indexOf("const url = 'http://localhost:'");
assert.ok(jsxGateIdx !== -1 && htmlFetchIdx > jsxGateIdx, 'JSX must return before /source fetch');
const jsxGate = injectFn.slice(jsxGateIdx, htmlFetchIdx);
assert.doesNotMatch(
jsxGate,
/replaceChild/,
'the JSX gate must not replaceChild a React tree',
);
assert.doesNotMatch(
jsxGate,
/discardOrphanedSession/,
'a missing JSX wrap must wait for mount, not discard as an orphan',
);
assert.match(
SOURCE,
/function normalizeSourceFallbackBlock\(block, filePath\)[\s\S]*?<style\\b\(\[\^>\]\*\)>\\s\*\\\{\\s\*`\(\[\\s\\S\]\*\?\)`\\s\*\\\}\\s\*<\\\/style>/,
'source fallback should unwrap JSX style template literals',
jsxGate,
/if \(!liveWrapper\) \{[\s\S]*?showToast\([\s\S]*?return;[\s\S]*?if \(liveWrapper\.dataset\.impeccableMode !== 'insert'\) \{[\s\S]*?recoverEmptyCycling\('source-fallback-empty'\)/,
'missing wrap waits; empty replace wrap recovers after retries; insert scaffolds stay',
);
assert.doesNotMatch(SOURCE, /function normalizeSourceFallbackBlock/);
assert.doesNotMatch(SOURCE, /function jsxStyleObjectToCss/);
assert.match(
injectFn,
/parser\.parseFromString\(block, 'text\/html'\)/,
'HTML fallback should parse the extracted marker block as HTML',
);
assert.match(
SOURCE,
/replace\(\/\\bclassName\\s\*=\/g, 'class='\)/,
'source fallback should translate className back to HTML class attributes',
);
assert.match(
SOURCE,
/value\.replace\(\/\\\$\\\{\[\^}\]\*\\\}\/g, ' '\)/,
'source fallback should reduce JSX template className values to literal class tokens',
injectFn,
/const startMark = '<!-- impeccable-variants-start ' \+ sessionId \+ ' -->'/,
'HTML fallback should still scan HTML comment markers',
);
assert.doesNotMatch(
SOURCE,
/querySelectorAll\(tag \+ '\\.' \+ cls\.split/,
'source fallback should not construct unsafe selectors from JSX-ish class strings',
);
assert.match(
SOURCE,
/function jsxStyleObjectToCss\(body\)/,
'source fallback should translate simple JSX style objects such as display:none',
);
});
it('does not source-inject per variant_progress checkpoint (HMR owns mid-generation reconciliation)', () => {