Where: assets/explainers-ui.js's inlineMarkdown (lines 33-45) - escapeHtml(text) runs on the whole raw text first, then escapeHtml(resolved) runs again on the URL extracted from inside it. The same underlying bug as the companion issue in scripts/build_explainers.py, but in the site's client-side renderer - a genuinely separate, independently-reachable code path.
The gap: this function is real, live code: index.html loads explainers-ui.js, whose renderDetailPage() fetches an explainer's raw .md file client-side (fetch(\explainers/${entry.slug}.md`)) and renders it via renderMarkdown()->inlineMarkdown(), independently of the pre-built static HTML pages scripts/build_explainers.py` generates server-side.
Repro:
function escapeHtml(value) {
return String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
function inlineMarkdown(text) {
const escaped = escapeHtml(text);
return escaped.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, url) =>
`<a href="${escapeHtml(url.trim())}">${label}</a>`);
}
console.log(inlineMarkdown('[cite](https://www.wicourts.gov/x.pdf?content=pdf&seqNo=188417)'));
// <a href="https://www.wicourts.gov/x.pdf?content=pdf&amp;seqNo=188417">cite</a>
Same double-escaping as the server-side generator - confirmed with the exact function bodies from assets/explainers-ui.js.
Why it matters: any explainer with an & in a link URL - including the State v. Loomis citation in automation-bias.md already confirmed broken in the companion server-side issue - breaks the same way if rendered through this client-side path, independently of whether the static HTML generator is ever fixed.
Suggested fix: apply the same fix as the companion scripts/build_explainers.py issue here too - extract the raw URL from unescaped text before escaping the surrounding line, or restructure inlineMarkdown so the URL substring is only ever escaped once.
Where:
assets/explainers-ui.js'sinlineMarkdown(lines 33-45) -escapeHtml(text)runs on the whole raw text first, thenescapeHtml(resolved)runs again on the URL extracted from inside it. The same underlying bug as the companion issue inscripts/build_explainers.py, but in the site's client-side renderer - a genuinely separate, independently-reachable code path.The gap: this function is real, live code:
index.htmlloadsexplainers-ui.js, whoserenderDetailPage()fetches an explainer's raw.mdfile client-side (fetch(\explainers/${entry.slug}.md`)) and renders it viarenderMarkdown()->inlineMarkdown(), independently of the pre-built static HTML pagesscripts/build_explainers.py` generates server-side.Repro:
Same double-escaping as the server-side generator - confirmed with the exact function bodies from
assets/explainers-ui.js.Why it matters: any explainer with an
&in a link URL - including the State v. Loomis citation inautomation-bias.mdalready confirmed broken in the companion server-side issue - breaks the same way if rendered through this client-side path, independently of whether the static HTML generator is ever fixed.Suggested fix: apply the same fix as the companion
scripts/build_explainers.pyissue here too - extract the raw URL from unescaped text before escaping the surrounding line, or restructureinlineMarkdownso the URL substring is only ever escaped once.