From 17240535310061b80e9a671090aaf168c1fb685b Mon Sep 17 00:00:00 2001 From: Siqi Zhang Date: Thu, 16 Apr 2026 17:50:53 -0400 Subject: [PATCH 1/4] add LaTeX bracket delimiter normalizer. --- src/utils/latexNormalizer.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/utils/latexNormalizer.ts diff --git a/src/utils/latexNormalizer.ts b/src/utils/latexNormalizer.ts new file mode 100644 index 00000000..940652af --- /dev/null +++ b/src/utils/latexNormalizer.ts @@ -0,0 +1,29 @@ +// remark-math only recognizes $-delimiters; rewrite \[..\] and \(..\) so KaTeX +// sees them. Fenced code blocks and inline code pass through untouched. + +const PROTECTED_SEGMENTS = /```[\s\S]*?```|`[^`\n]*`/g; +const MATH_DELIMITERS = /\\\[([\s\S]+?)\\\]|\\\(([^\n]+?)\\\)/g; + +export function normalizeLatexDelimiters(input: string): string { + if (input.length === 0) return input; + if (!input.includes('\\[') && !input.includes('\\(')) return input; + + let output = ''; + let cursor = 0; + + for (const match of input.matchAll(PROTECTED_SEGMENTS)) { + const start = match.index; + output += rewriteMathDelimiters(input.slice(cursor, start)); + output += match[0]; + cursor = start + match[0].length; + } + output += rewriteMathDelimiters(input.slice(cursor)); + return output; +} + +function rewriteMathDelimiters(segment: string): string { + if (!segment) return segment; + return segment.replace(MATH_DELIMITERS, (_, display, inline) => + display !== undefined ? `$$${display}$$` : `$${inline}$`, + ); +} From 293b10dae55326622a88df7c7e6e788fb1df831d Mon Sep 17 00:00:00 2001 From: Siqi Zhang Date: Thu, 16 Apr 2026 18:02:20 -0400 Subject: [PATCH 2/4] =?UTF-8?q?fix(chat):=20render=20\[..\]=20/=20\(..\)?= =?UTF-8?q?=20LaTeX=20in=20chat=20messages.=20Wire=20normalizeLatexDelimit?= =?UTF-8?q?ers=20into=20unescapeWithMathProtection=20so=20bracket-delimite?= =?UTF-8?q?d=20math=20is=20rewritten=20to=20$-delimiters=20before=20the=20?= =?UTF-8?q?mask/unescape=20step.=20Without=20this,=20\[..\]=20and=20\(..\)?= =?UTF-8?q?=20rendered=20as=20literal=20text,=20and=20the=20\\t=20?= =?UTF-8?q?=E2=86=92=20\t=20replacement=20inside=20unescapeWithMathProtect?= =?UTF-8?q?ion=20corrupted=20\theta=20/=20\tau=20/=20\n-commands=20that=20?= =?UTF-8?q?sat=20inside=20bracket-delimited=20blocks=20(issue=20#50).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/chat/utils/chatFormatting.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/chat/utils/chatFormatting.ts b/src/components/chat/utils/chatFormatting.ts index c838d509..7b12da54 100644 --- a/src/components/chat/utils/chatFormatting.ts +++ b/src/components/chat/utils/chatFormatting.ts @@ -1,3 +1,5 @@ +import { normalizeLatexDelimiters } from '../../../utils/latexNormalizer'; + export function decodeHtmlEntities(text: string) { if (!text) return text; return text @@ -20,11 +22,16 @@ export function normalizeInlineCodeFences(text: string) { export function unescapeWithMathProtection(text: string) { if (!text || typeof text !== 'string') return text; + // Rewrite \[..\] / \(..\) into $-delimited form before masking — the mask + // below only covers $-delimiters, so without this the \\t → \t replacement + // would corrupt \theta / \tau / \n-commands inside bracket-delimited math. + let processedText = normalizeLatexDelimiters(text); + const mathBlocks: string[] = []; const placeholderPrefix = '__MATH_BLOCK_'; const placeholderSuffix = '__'; - let processedText = text.replace(/\$\$([\s\S]*?)\$\$|\$([^\$\n]+?)\$/g, (match) => { + processedText = processedText.replace(/\$\$([\s\S]*?)\$\$|\$([^\$\n]+?)\$/g, (match) => { const index = mathBlocks.length; mathBlocks.push(match); return `${placeholderPrefix}${index}${placeholderSuffix}`; From c9d3ae4983dcb06aef624be1f19897e962c31010 Mon Sep 17 00:00:00 2001 From: Siqi Zhang Date: Thu, 16 Apr 2026 18:16:38 -0400 Subject: [PATCH 3/4] refactor: integrate normalizeLatexDelimiters for consistent LaTeX rendering across components - Added normalizeLatexDelimiters to CodeEditor, ResearchLab, ChatContextFilePreview, and SurveyPage components to ensure LaTeX content is properly formatted before rendering. - This change enhances the handling of LaTeX delimiters, improving the overall user experience when displaying mathematical content. --- src/components/CodeEditor.jsx | 3 ++- src/components/ResearchLab.jsx | 7 ++++--- .../chat/view/subcomponents/ChatContextFilePreview.tsx | 3 ++- src/components/survey/view/SurveyPage.tsx | 10 +++++++++- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/components/CodeEditor.jsx b/src/components/CodeEditor.jsx index f14de7a8..99262590 100644 --- a/src/components/CodeEditor.jsx +++ b/src/components/CodeEditor.jsx @@ -17,6 +17,7 @@ import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; import rehypeRaw from 'rehype-raw'; +import { normalizeLatexDelimiters } from '../utils/latexNormalizer'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneDark as prismOneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { api } from '../utils/api'; @@ -138,7 +139,7 @@ function MarkdownPreview({ content }) { rehypePlugins={rehypePlugins} components={markdownPreviewComponents} > - {content} + {normalizeLatexDelimiters(content ?? '')} ); } diff --git a/src/components/ResearchLab.jsx b/src/components/ResearchLab.jsx index 80073f3c..1f52e60e 100644 --- a/src/components/ResearchLab.jsx +++ b/src/components/ResearchLab.jsx @@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; +import { normalizeLatexDelimiters } from '../utils/latexNormalizer'; import { FlaskConical, RefreshCw, FileText, BookOpen, Settings2, Lightbulb, GitBranch, FolderOpen, ChevronDown, ChevronRight, ExternalLink, @@ -755,7 +756,7 @@ function OverviewCard({ instance, config, researchBrief, compact = false }) { rehypePlugins={[rehypeKatex]} components={OVERVIEW_MARKDOWN_COMPONENTS} > - {section.content} + {normalizeLatexDelimiters(section.content)} @@ -1915,7 +1916,7 @@ function IdeaCard({ projectName, config, projectFileSet, compact = false }) { rehypePlugins={rehypePlugins} components={ideaMarkdownComponents} > - {ideaText} + {normalizeLatexDelimiters(ideaText ?? '')} )} @@ -2244,7 +2245,7 @@ function FileViewer({ projectName, file, onClose }) { rehypePlugins={[rehypeKatex]} components={markdownComponents} > - {content} + {normalizeLatexDelimiters(content ?? '')} diff --git a/src/components/chat/view/subcomponents/ChatContextFilePreview.tsx b/src/components/chat/view/subcomponents/ChatContextFilePreview.tsx index 539d2b4e..f5c43db0 100644 --- a/src/components/chat/view/subcomponents/ChatContextFilePreview.tsx +++ b/src/components/chat/view/subcomponents/ChatContextFilePreview.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import ReactMarkdown from 'react-markdown'; +import { normalizeLatexDelimiters } from '../../../../utils/latexNormalizer'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; @@ -307,7 +308,7 @@ export default function ChatContextFilePreview({
- {content} + {normalizeLatexDelimiters(content)}
diff --git a/src/components/survey/view/SurveyPage.tsx b/src/components/survey/view/SurveyPage.tsx index b40fdda8..adbba53f 100644 --- a/src/components/survey/view/SurveyPage.tsx +++ b/src/components/survey/view/SurveyPage.tsx @@ -2,6 +2,9 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; +import remarkMath from 'remark-math'; +import rehypeKatex from 'rehype-katex'; +import { normalizeLatexDelimiters } from '../../../utils/latexNormalizer'; import { BookOpen, FileText, @@ -250,7 +253,12 @@ function PreviewContent({ {file.previewKind === 'markdown' && preview.content ? (
- {preview.content} + + {normalizeLatexDelimiters(preview.content)} +
) : null} From 4db27bb7d1e8e34d52f8c51ac6b43b35af7cc761 Mon Sep 17 00:00:00 2001 From: Siqi Zhang <143839026+MarkSiqiZhang@users.noreply.github.com> Date: Thu, 16 Apr 2026 19:14:21 -0400 Subject: [PATCH 4/4] Potential fix for pull request finding 'Useless conditional' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- src/components/ResearchLab.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ResearchLab.jsx b/src/components/ResearchLab.jsx index 1f52e60e..316937d5 100644 --- a/src/components/ResearchLab.jsx +++ b/src/components/ResearchLab.jsx @@ -1916,7 +1916,7 @@ function IdeaCard({ projectName, config, projectFileSet, compact = false }) { rehypePlugins={rehypePlugins} components={ideaMarkdownComponents} > - {normalizeLatexDelimiters(ideaText ?? '')} + {normalizeLatexDelimiters(ideaText)} )}