diff --git a/packages/coding-agent/scripts/generate-template.ts b/packages/coding-agent/scripts/generate-template.ts
index 2bc95be57..8d2ae3c4f 100644
--- a/packages/coding-agent/scripts/generate-template.ts
+++ b/packages/coding-agent/scripts/generate-template.ts
@@ -18,10 +18,11 @@ const minifiedCss = css
.replace(/\s*([{}:;,])\s*/g, "$1")
.trim();
-// Inline everything
+// Inline everything; use function replacements so `$'`, `$&`, `$$`, etc. inside
+// the embedded CSS/JS are not interpreted as substitution patterns.
const template = html
- .replace("", ``)
- .replace("", ``);
+ .replace("", () => ``)
+ .replace("", () => ``);
// Write generated file
const output = `// Auto-generated by scripts/generate-template.ts - DO NOT EDIT
diff --git a/packages/coding-agent/src/export/html/index.ts b/packages/coding-agent/src/export/html/index.ts
index 71f367188..5f1b010ff 100644
--- a/packages/coding-agent/src/export/html/index.ts
+++ b/packages/coding-agent/src/export/html/index.ts
@@ -103,9 +103,12 @@ async function generateHtml(sessionData: SessionData, themeName?: string): Promi
const themeVars = await generateThemeVars(themeName);
const sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toBase64();
- return TEMPLATE.replace("", ``).replace(
+ // Use function replacements so `$'`, `$&`, `$$`, `$n`, etc. in the
+ // substituted CSS/base64 are not interpreted as substitution patterns
+ // (see https://mdn.io/String.replace).
+ return TEMPLATE.replace("", () => ``).replace(
"{{SESSION_DATA}}",
- sessionDataBase64,
+ () => sessionDataBase64,
);
}
diff --git a/packages/coding-agent/src/export/html/template.generated.ts b/packages/coding-agent/src/export/html/template.generated.ts
index 530451d87..954a176b0 100644
--- a/packages/coding-agent/src/export/html/template.generated.ts
+++ b/packages/coding-agent/src/export/html/template.generated.ts
@@ -1,2 +1,2 @@
// Auto-generated by scripts/generate-template.ts - DO NOT EDIT
-export const TEMPLATE = "\n\n
\n \n \n Session Export\n \n \n\n\n \n \n \n \n \n
\n \n \n \n
\n

\n
\n
\n\n \n \n \n \n\n\n";
+export const TEMPLATE = "\n\n\n \n \n Session Export\n \n \n\n\n \n \n \n \n \n
\n \n \n \n
\n

\n
\n
\n\n \n \n \n \n\n\n";
diff --git a/packages/coding-agent/src/export/html/template.macro.ts b/packages/coding-agent/src/export/html/template.macro.ts
index 86bef1258..26a532189 100644
--- a/packages/coding-agent/src/export/html/template.macro.ts
+++ b/packages/coding-agent/src/export/html/template.macro.ts
@@ -17,8 +17,9 @@ export async function getTemplate(): Promise {
.replace(/\s*([{}:;,])\s*/g, "$1")
.trim();
- // Inline everything
+ // Inline everything; use function replacements so `$'`, `$&`, `$$`, etc.
+ // inside the embedded CSS/JS are not interpreted as substitution patterns.
return html
- .replace("", ``)
- .replace("", ``);
+ .replace("", () => ``)
+ .replace("", () => ``);
}
diff --git a/packages/coding-agent/test/export/html-template-script-substitution.test.ts b/packages/coding-agent/test/export/html-template-script-substitution.test.ts
new file mode 100644
index 000000000..dbdf2f9ca
--- /dev/null
+++ b/packages/coding-agent/test/export/html-template-script-substitution.test.ts
@@ -0,0 +1,41 @@
+import { describe, expect, it } from "bun:test";
+import { TEMPLATE } from "../../src/export/html/template.generated";
+
+// Regression: `String.prototype.replace(string, string)` treats `$'`, `$&`,
+// `$$`, `$n`, etc. as substitution patterns. The inlined `