diff --git a/frontend/src/core/artifacts/preview.ts b/frontend/src/core/artifacts/preview.ts
index da222a88a..1884dc1f3 100644
--- a/frontend/src/core/artifacts/preview.ts
+++ b/frontend/src/core/artifacts/preview.ts
@@ -175,8 +175,14 @@ export function appendHtmlPreviewBaseHref(
const baseHref = htmlBaseHref(url, currentHref);
const baseElement = ``;
- if (/
]*>/i.exec(content)) {
- return content.replace(/]*)>/i, `${baseElement}`);
+ // "(?:\s[^>]*)?" keeps the tag-name boundary so `` (a common
+ // leading tag in agent-generated fragments) is not mistaken for ``;
+ // mirrors appendHtmlPreviewScrollRestoration below.
+ if (/]*)?>/i.test(content)) {
+ return content.replace(
+ /]*)?>/i,
+ (headTag) => `${headTag}${baseElement}`,
+ );
}
return `${baseElement}${content}`;
}
diff --git a/frontend/tests/unit/core/artifacts/preview.test.ts b/frontend/tests/unit/core/artifacts/preview.test.ts
index 8544188be..4d68d890c 100644
--- a/frontend/tests/unit/core/artifacts/preview.test.ts
+++ b/frontend/tests/unit/core/artifacts/preview.test.ts
@@ -282,6 +282,39 @@ test("preserves existing head elements when injecting scroll restoration", () =>
);
});
+test("does not mistake for when injecting the base href", () => {
+ // Agent-generated fragments often have no but open with ;
+ // the base must then be prepended so assets before the tag resolve too.
+ const html =
+ '
content';
+
+ const result = appendHtmlPreviewBaseHref(
+ html,
+ "/demo/threads/thread-1/user-data/outputs/report.html",
+ "http://localhost/workspace/chats/thread-1",
+ );
+
+ expect(result).toBe(
+ '' +
+ html,
+ );
+});
+
+test("injects the base href inside an attributed tag", () => {
+ const html =
+ 'x';
+
+ const result = appendHtmlPreviewBaseHref(
+ html,
+ "/demo/threads/thread-1/user-data/outputs/report.html",
+ "http://localhost/workspace/chats/thread-1",
+ );
+
+ expect(result).toContain(
+ '',
+ );
+});
+
test("does not duplicate HTML scroll restoration script", () => {
const html = appendHtmlPreviewScrollRestoration(
"x",