mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-19 01:26:29 +03:00
* 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:
@@ -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)', () => {
|
||||
|
||||
Reference in New Issue
Block a user