diff --git a/apps/app/src/components/generative-ui/open-generative-ui/__tests__/frame-content.test.ts b/apps/app/src/components/generative-ui/open-generative-ui/__tests__/frame-content.test.ts
new file mode 100644
index 0000000..db441fa
--- /dev/null
+++ b/apps/app/src/components/generative-ui/open-generative-ui/__tests__/frame-content.test.ts
@@ -0,0 +1,29 @@
+import { describe, it, expect } from "vitest";
+import { buildFinalFrameContent } from "../frame-content";
+
+describe("buildFinalFrameContent", () => {
+ // Websandbox rejects frame content without a literal "
" and injects
+ // its bootstrap at that tag.
+ it.each([
+ ['Tx'],
+ ["Tx"],
+ ])("normalizes a head tag websandbox would not find: %s", (html) => {
+ const frame = buildFinalFrameContent(html);
+ expect(frame).toContain("");
+ expect(frame.indexOf("Content-Security-Policy")).toBeGreaterThan(
+ frame.indexOf("")
+ );
+ expect(frame.indexOf("Content-Security-Policy")).toBeLessThan(
+ frame.search(//i)
+ );
+ });
+
+ it("does not mistake for the head", () => {
+ const frame = buildFinalFrameContent("");
+ expect(frame.indexOf("")).toBe(0);
+ expect(frame).toContain("");
+ expect(frame.indexOf("Content-Security-Policy")).toBeLessThan(
+ frame.indexOf("")
+ );
+ });
+});
diff --git a/apps/app/src/components/generative-ui/open-generative-ui/frame-content.ts b/apps/app/src/components/generative-ui/open-generative-ui/frame-content.ts
index 246e696..d2e7b02 100644
--- a/apps/app/src/components/generative-ui/open-generative-ui/frame-content.ts
+++ b/apps/app/src/components/generative-ui/open-generative-ui/frame-content.ts
@@ -38,8 +38,16 @@ export const CSP_META_TAG = ``;
+const HEAD_OPEN_TAG = /]*)?>/i;
+
+/**
+ * Websandbox requires a literal lowercase "" in its frame content and
+ * injects its bootstrap there, so "" or "" is normalized
+ * (attributes on have no effect) and a missing head is added.
+ */
export function ensureHead(html: string): string {
- if (/]/i.test(html)) return html;
+ if (html.includes("")) return html;
+ if (HEAD_OPEN_TAG.test(html)) return html.replace(HEAD_OPEN_TAG, "");
return `${html}`;
}
@@ -58,11 +66,7 @@ export function buildFinalFrameContent(html: string, css?: string): string {
DESIGN_SYSTEM_STYLE_TAG +
(css ? `` : "");
const withHead = ensureHead(html);
- const openTag = withHead.match(/]*>/i);
- if (!openTag || openTag.index === undefined) {
- return `${headContent}${withHead}`;
- }
- const insertAt = openTag.index + openTag[0].length;
+ const insertAt = withHead.indexOf("") + "".length;
return withHead.slice(0, insertAt) + headContent + withHead.slice(insertAt);
}