Skip to content

Commit bed4458

Browse files
fix(runtime): inject comment overlay into html previews
1 parent b614196 commit bed4458

2 files changed

Lines changed: 39 additions & 6 deletions

File tree

packages/runtime/src/index.test.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,29 @@ describe('buildSrcdoc', () => {
99
expect(out).not.toContain('Content-Security-Policy');
1010
});
1111

12-
it('passes legacy full-HTML documents through verbatim (pre-JSX-only snapshots)', () => {
12+
it('keeps legacy full-HTML documents as HTML but injects the preview overlay', () => {
1313
// Snapshots written before the JSX-only switchover contain raw HTML
1414
// documents. Wrapping those as JSX makes Babel bark on the DOCTYPE /
15-
// <html> tokens, so buildSrcdoc short-circuits when it detects a
16-
// full HTML document and returns it unchanged.
15+
// <html> tokens, so buildSrcdoc injects the preview overlay without
16+
// routing them through the React+Babel wrapper.
1717
const html = '<html><body><p>x</p></body></html>';
18-
expect(buildSrcdoc(html)).toBe(html);
18+
const out = buildSrcdoc(html);
19+
expect(out).toContain('<p>x</p>');
20+
expect(out).toContain('CODESIGN_OVERLAY_SCRIPT');
21+
expect(out).toContain('ELEMENT_SELECTED');
22+
expect(out).not.toContain('AGENT_BODY_BEGIN');
23+
1924
const doctyped = '<!DOCTYPE html><html><body><p>y</p></body></html>';
20-
expect(buildSrcdoc(doctyped)).toBe(doctyped);
25+
const doctypedOut = buildSrcdoc(doctyped);
26+
expect(doctypedOut).toContain('<p>y</p>');
27+
expect(doctypedOut).toContain('CODESIGN_OVERLAY_SCRIPT');
28+
expect(doctypedOut).not.toContain('AGENT_BODY_BEGIN');
29+
});
30+
31+
it('does not duplicate the overlay when a full-HTML document is rebuilt', () => {
32+
const once = buildSrcdoc('<html><body><p>x</p></body></html>');
33+
const twice = buildSrcdoc(once);
34+
expect(twice).toBe(once);
2135
});
2236

2337
it('wraps a fragment via the JSX path (no legacy HTML branch)', () => {

packages/runtime/src/index.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type { IframeErrorMessage } from './iframe-errors';
3333

3434
const JSX_TEMPLATE_BEGIN = '<!-- AGENT_BODY_BEGIN -->';
3535
const JSX_TEMPLATE_END = '<!-- AGENT_BODY_END -->';
36+
const OVERLAY_MARKER = '<!-- CODESIGN_OVERLAY_SCRIPT -->';
3637

3738
function escapeForScriptLiteral(jsx: string): string {
3839
// JSON.stringify handles quotes/newlines; the </script> escape prevents the
@@ -80,6 +81,24 @@ ${JSX_TEMPLATE_END}
8081
</html>`;
8182
}
8283

84+
function overlayScriptTag(): string {
85+
return `${OVERLAY_MARKER}<script>${OVERLAY_SCRIPT}</script>`;
86+
}
87+
88+
function injectOverlayIntoHtmlDocument(html: string): string {
89+
if (html.includes(OVERLAY_MARKER) || html.includes("type: 'ELEMENT_SELECTED'")) {
90+
return html;
91+
}
92+
const script = overlayScriptTag();
93+
if (/<\/body\s*>/i.test(html)) {
94+
return html.replace(/<\/body\s*>/i, `${script}</body>`);
95+
}
96+
if (/<\/html\s*>/i.test(html)) {
97+
return html.replace(/<\/html\s*>/i, `${script}</html>`);
98+
}
99+
return `${html}${script}`;
100+
}
101+
83102
/**
84103
* Wrap an agent artifact in the vendored React + Babel skeleton, ready for
85104
* use as an iframe `srcdoc`. Already-wrapped payloads pass through unchanged.
@@ -108,7 +127,7 @@ export function buildSrcdoc(userSource: string): string {
108127
// Legacy HTML document (pre-JSX-only-switchover snapshots) — render as-is.
109128
const head = stripped.trimStart().slice(0, 2048).toLowerCase();
110129
if (head.startsWith('<!doctype') || head.startsWith('<html')) {
111-
return stripped;
130+
return injectOverlayIntoHtmlDocument(stripped);
112131
}
113132
return wrapJsxAsSrcdoc(stripped);
114133
}

0 commit comments

Comments
 (0)