Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -647,4 +647,22 @@ describe("OpenGenUIActivityRenderer", () => {
});
expect(parsed.success).toBe(true);
});

it("keeps later streamed expressions behind an unresolved earlier batch", async () => {
let release!: () => void;
const pending = new Promise<void>((resolve) => { release = resolve; });
mockRun.mockImplementation((code) => code === "first()" ? pending : Promise.resolve());
const base = { html: ["<body>Ready</body>"], htmlComplete: true, generating: false };
try {
const { rerender } = renderRenderer({ ...base, jsExpressions: ["first()", "second()"] });
await flushImport(); await resolveSandboxReady();
rerender(rendererElement({ ...base, jsExpressions: ["first()", "second()", "third()"] }));
await flushImport();
const calls = () => mockRun.mock.calls.map((call) => call[0]).filter((code) => ["first()", "second()", "third()"].includes(code));
expect(calls()).toEqual(["first()"]);
release(); await flushImport();
expect(calls()).toEqual(["first()", "second()", "third()"]);
} finally { release(); mockRun.mockResolvedValue(undefined); }
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,16 @@ const OpenGenUIActivityRendererInner = React.memo(
const sandboxReadyRef = useRef(false);
const executedIndexRef = useRef(0);
const pendingQueueRef = useRef<string[]>([]);
const executionChainRef = useRef<Promise<unknown>>(Promise.resolve());
const enqueueCode = useCallback((sandbox: SandboxInstance, code: string) => {
executionChainRef.current = executionChainRef.current
.then(() => {
if (sandboxRef.current === sandbox) return sandbox.run(code);
})
.catch((error: unknown) => {
console.error("[OpenGenUI] Sandbox script failed:", error);
});
}, []);
const jsFunctionsInjectedRef = useRef(false);

// Effect 0 — Preview sandbox creation
Expand Down Expand Up @@ -261,6 +271,7 @@ const OpenGenUIActivityRendererInner = React.memo(
jsFunctionsInjectedRef.current = false;
sandboxReadyRef.current = false;
pendingQueueRef.current = [];
executionChainRef.current = Promise.resolve();

// Continuous autosize: track every resize report from this sandbox.
const onMessage = (e: MessageEvent) => {
Expand Down Expand Up @@ -296,11 +307,7 @@ const OpenGenUIActivityRendererInner = React.memo(

const queue = pendingQueueRef.current;
pendingQueueRef.current = [];
(async () => {
for (const code of queue) {
await sandbox.run(code);
}
})();
for (const code of queue) enqueueCode(sandbox, code);
});
})
.catch((err: unknown) => {
Expand All @@ -322,7 +329,7 @@ const OpenGenUIActivityRendererInner = React.memo(
sandboxReadyRef.current = false;
setAutoHeight(null);
};
}, [fullHtml, css, localApi]);
}, [fullHtml, css, localApi, enqueueCode]);

// Effect 2 — jsFunctions injection (once per final sandbox)
useEffect(() => {
Expand All @@ -331,11 +338,11 @@ const OpenGenUIActivityRendererInner = React.memo(

const sandbox = sandboxRef.current;
if (sandboxReadyRef.current && sandbox) {
sandbox.run(content.jsFunctions);
enqueueCode(sandbox, content.jsFunctions);
} else {
pendingQueueRef.current.push(content.jsFunctions);
}
}, [content.jsFunctions]);
}, [content.jsFunctions, enqueueCode]);

// Effect 3 — jsExpressions execution (arrival order, sequential await)
useEffect(() => {
Expand All @@ -350,15 +357,11 @@ const OpenGenUIActivityRendererInner = React.memo(

const sandbox = sandboxRef.current;
if (sandboxReadyRef.current && sandbox) {
(async () => {
for (const expr of newExprs) {
await sandbox.run(expr);
}
})();
for (const expr of newExprs) enqueueCode(sandbox, expr);
} else {
pendingQueueRef.current.push(...newExprs);
}
}, [content.jsExpressions?.length]); // eslint-disable-line react-hooks/exhaustive-deps
}, [content.jsExpressions?.length, enqueueCode]); // eslint-disable-line react-hooks/exhaustive-deps

// Effect 4 — destroy any remaining sandbox on unmount
useEffect(() => {
Expand Down