From e4f58e0d6939486fc0e0951d70fb39c49a6aae9d Mon Sep 17 00:00:00 2001 From: rissrice2105-agent Date: Sun, 21 Jun 2026 19:06:25 -0600 Subject: [PATCH] fix(chat): avoid code block placeholder collisions --- src/lib/utils/url-link-converter.js | 22 +++++++++++++++----- tests/url-link-converter-placeholder.test.js | 14 +++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 tests/url-link-converter-placeholder.test.js diff --git a/src/lib/utils/url-link-converter.js b/src/lib/utils/url-link-converter.js index cff1cc1..8de7459 100644 --- a/src/lib/utils/url-link-converter.js +++ b/src/lib/utils/url-link-converter.js @@ -42,9 +42,12 @@ export function convertUrlsToLinks(text) { // Extract code blocks and replace with placeholders while ((codeMatch = codeBlockRegex.exec(text)) !== null) { - const placeholder = `__CODE_BLOCK_${codeIndex}__`; + const placeholder = createCodeBlockPlaceholder(text, codeIndex); const codeContent = codeMatch[1]; - codeBlocks.push(`
${escapeHtml(codeContent)}
`); + codeBlocks.push({ + placeholder, + html: `
${escapeHtml(codeContent)}
` + }); textWithPlaceholders = textWithPlaceholders.replace(codeMatch[0], placeholder); codeIndex++; } @@ -86,10 +89,19 @@ export function convertUrlsToLinks(text) { }); // Restore code blocks from placeholders - codeBlocks.forEach((codeBlock, index) => { - const placeholder = `__CODE_BLOCK_${index}__`; - result = result.replace(placeholder, codeBlock); + codeBlocks.forEach((codeBlock) => { + result = result.replace(codeBlock.placeholder, codeBlock.html); }); return result; } + +function createCodeBlockPlaceholder(text, index) { + let suffix = 0; + let placeholder; + do { + placeholder = `\u0000QRYPTCHAT_CODE_BLOCK_${index}_${suffix}\u0000`; + suffix++; + } while (text.includes(placeholder)); + return placeholder; +} diff --git a/tests/url-link-converter-placeholder.test.js b/tests/url-link-converter-placeholder.test.js new file mode 100644 index 0000000..ec38899 --- /dev/null +++ b/tests/url-link-converter-placeholder.test.js @@ -0,0 +1,14 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { convertUrlsToLinks } from '../src/lib/utils/url-link-converter.js'; + +describe('convertUrlsToLinks code block placeholders', () => { + it('does not replace user-authored placeholder-like text with code block HTML', () => { + const html = convertUrlsToLinks('literal __CODE_BLOCK_0__ here\n```js\nconsole.log(1)\n```'); + + assert.match(html, /literal __CODE_BLOCK_0__ here/); + assert.match(html, /
js\nconsole\.log\(1\)\n<\/code><\/pre>/);
+		assert.doesNotMatch(html, /
__CODE_BLOCK_0__$/); + }); +});