#22695: fix(export): restore HTML template placeholders broken by formatter (#22595)
size: S
experienced-contributor
## Summary
- **Bug**: `/export-session` generates broken HTML — vendored JS not embedded, page non-functional
- **Root cause 1**: `template.html` placeholders `{{MARKED_JS}}`, `{{HIGHLIGHT_JS}}`, `{{JS}}` were reformatted into multi-line `{ { MARKED_JS; } }` by a code formatter, so `.replace("{{MARKED_JS}}", ...)` silently matched nothing
- **Root cause 2**: `highlight.min.js` contains literal `$&` (R language regex pattern), which JavaScript's `String.replace()` interprets as "insert matched substring" — causing `{{HIGHLIGHT_JS}}` to be re-inserted into the output even after replacement
- **Fix**: Restore placeholders to single-line form with `<!-- prettier-ignore -->` guards, and switch all `.replace()` calls to use arrow-function replacers to prevent `$` special pattern injection
Fixes #22595
## Problem
Two independent bugs prevented `/export-session` from producing functional HTML:
### Bug 1: Formatter broke template placeholders
The export-session HTML template had its mustache-style placeholders broken by a formatter (likely Prettier):
```html
<!-- Before (broken by formatter) -->
<script>
{
{
MARKED_JS;
}
}
</script>
<!-- After (fixed) -->
<!-- prettier-ignore -->
<script>{{MARKED_JS}}</script>
```
### Bug 2: `$&` in vendor JS causes replacement injection
`highlight.min.js` contains `$&` as part of R language regex patterns. When used as a plain string replacement value in `String.replace("{{HIGHLIGHT_JS}}", hljsJs)`, JavaScript interprets `$&` as "insert the matched substring" — re-inserting `{{HIGHLIGHT_JS}}` back into the output.
Fix: use arrow-function replacers which bypass `$` special pattern interpretation:
```typescript
// Before (buggy)
template.replace("{{HIGHLIGHT_JS}}", hljsJs)
// After (fixed)
template.replace("{{HIGHLIGHT_JS}}", () => hljsJs)
```
**Before fix:**
- HTML file ~20 KB (vendor JS missing)
- Browser console: `ReferenceError: MARKED_JS is not defined` (3 errors)
- Page completely non-functional — blank dark background
<img width="2956" height="878" alt="image" src="https://github.com/user-attachments/assets/c300f208-a9d9-4fcb-9a3a-40ce94354bf4" />
<img width="1742" height="478" alt="image" src="https://github.com/user-attachments/assets/98b1f577-3dab-4f7d-addd-7a4f98503e88" />
**After fix:**
- HTML file ~240 KB (all vendor JS embedded)
- Session renders correctly with markdown, code highlighting, sidebar tree, stats
<img width="1635" height="1066" alt="image" src="https://github.com/user-attachments/assets/4daf8dee-324e-4ce2-b742-0b4eec56917b" />
## Changes
- `src/auto-reply/reply/export-html/template.html` — Restore `{{MARKED_JS}}`, `{{HIGHLIGHT_JS}}`, `{{JS}}` to single-line form; add `<!-- prettier-ignore -->` comments to prevent future formatter breakage
- `src/auto-reply/reply/commands-export-session.ts` — Switch all 5 `.replace()` calls in `generateHtml()` to use arrow-function replacers, preventing `$&` pattern injection from vendor JS content
- `src/auto-reply/reply/export-session-html.test.ts` — New: 2 regression tests verifying placeholders exist and full pipeline produces valid self-contained HTML
- `CHANGELOG.md` — Add fix entry
## Test plan
- [x] New test: template.html contains single-line `{{MARKED_JS}}`, `{{HIGHLIGHT_JS}}`, `{{JS}}` placeholders
- [x] New test: full generateHtml pipeline — all placeholders replaced, vendor JS embedded, CSS themed, HTML structure intact
- [x] All 2 new tests pass
- [x] Generated HTML verified in browser (before: blank page with 3 JS errors; after: fully functional session viewer)
## Effect on User Experience
**Before:** `/export-session` produces a broken HTML file (~20 KB). Opening it shows a blank dark page with JavaScript errors in the console. Users cannot view exported sessions.
**After:** `/export-session` produces a fully functional self-contained HTML file (~240 KB) with embedded JS libraries, rendering the session with markdown, syntax highlighting, sidebar navigation, and session stats.
Most Similar PRs
#22309: fix(export-session): render system prompt section and payload corre...
by AIflow-Labs · 2026-02-21
80.6%
#20816: fix(export-session): prevent Prettier from corrupting template plac...
by boris721 · 2026-02-19
78.7%
#22776: fix: restore export-session HTML template placeholders broken by Pr...
by miloudbelarebia · 2026-02-21
78.6%
#20423: fix(web-fetch): cap htmlToMarkdown input size to prevent catastroph...
by Limitless2023 · 2026-02-18
66.4%
#20419: fix(webchat): explicitly pass gfm and breaks options to marked.parse()
by Limitless2023 · 2026-02-18
65.7%
#16733: fix(ui): avoid injected newlines when tool output is hidden
by jp117 · 2026-02-15
63.3%
#2544: fix(security): XSS vulnerability in Canvas Host + Windows CI stability
by Kiwitwitter · 2026-01-27
62.6%
#20405: feat(ui): KaTeX math rendering, collapsible tool cards, image attac...
by ayaanngandhi · 2026-02-18
62.6%
#17530: refactor(sessions): centralize session status field extraction
by Facens · 2026-02-15
62.5%
#4567: Webchat: canonicalize main session key for /new (fix #4446)
by selfboot · 2026-01-30
62.2%