diff --git a/tools/server/public/index.html.gz b/tools/server/public/index.html.gz index 4cff76429e1..3fd631b77a8 100644 Binary files a/tools/server/public/index.html.gz and b/tools/server/public/index.html.gz differ diff --git a/tools/server/webui/src/lib/utils/latex-protection.test.ts b/tools/server/webui/src/lib/utils/latex-protection.test.ts index 2354f8fa0ec..40fe1b0db27 100644 --- a/tools/server/webui/src/lib/utils/latex-protection.test.ts +++ b/tools/server/webui/src/lib/utils/latex-protection.test.ts @@ -303,6 +303,27 @@ $$\n\\pi_n(\\mathbb{S}^3) = \\begin{cases} expect(output).toBe(input); // Code blocks prevent misinterpretation }); + test('preserves backslash parentheses in code blocks (GitHub issue)', () => { + const input = '```python\nfoo = "\\(bar\\)"\n```'; + const output = preprocessLaTeX(input); + + expect(output).toBe(input); // Code blocks should not have LaTeX conversion applied + }); + + test('preserves backslash brackets in code blocks', () => { + const input = '```python\nfoo = "\\[bar\\]"\n```'; + const output = preprocessLaTeX(input); + + expect(output).toBe(input); // Code blocks should not have LaTeX conversion applied + }); + + test('preserves backslash parentheses in inline code', () => { + const input = 'Use `foo = "\\(bar\\)"` in your code.'; + const output = preprocessLaTeX(input); + + expect(output).toBe(input); + }); + test('escape backslash in mchem ce', () => { const input = 'mchem ce:\n$\\ce{2H2(g) + O2(g) -> 2H2O(l)}$'; const output = preprocessLaTeX(input); diff --git a/tools/server/webui/src/lib/utils/latex-protection.ts b/tools/server/webui/src/lib/utils/latex-protection.ts index 7f5cf2cddfa..cafa2d4761f 100644 --- a/tools/server/webui/src/lib/utils/latex-protection.ts +++ b/tools/server/webui/src/lib/utils/latex-protection.ts @@ -226,19 +226,16 @@ export function preprocessLaTeX(content: string): string { return expr; }); - // Step 5: Restore code blocks - content = content.replace(/<>/g, (_, index) => { - return codeBlocks[parseInt(index)]; - }); - - // Step 6: Apply additional escaping functions (brackets and mhchem) + // Step 5: Apply additional escaping functions (brackets and mhchem) + // This must happen BEFORE restoring code blocks to avoid affecting code content content = escapeBrackets(content); if (doEscapeMhchem && (content.includes('\\ce{') || content.includes('\\pu{'))) { content = escapeMhchem(content); } - // Final pass: Convert \(...\) → $...$, \[...\] → $$...$$ + // Step 6: Convert remaining \(...\) → $...$, \[...\] → $$...$$ + // This must happen BEFORE restoring code blocks to avoid affecting code content content = content // Using the look‑behind pattern `(? { - return `${prefix}$$${content}$$`; + (_, content: string) => { + return `$$${content}$$`; } ); - // Step 7: Restore blockquote markers + // Step 7: Restore code blocks + // This happens AFTER all LaTeX conversions to preserve code content + content = content.replace(/<>/g, (_, index) => { + return codeBlocks[parseInt(index)]; + }); + + // Step 8: Restore blockquote markers if (blockquoteMarkers.size > 0) { const finalLines = content.split('\n'); const restoredLines = finalLines.map((line, index) => {