fix(web): defer math rendering while streaming

This commit is contained in:
ZiyaZhang
2026-08-01 00:58:50 -07:00
parent 1e334fa955
commit 8ce4f07f92
2 files changed
+21 -5

No files matched your search

@@ -8,8 +8,9 @@ import { CodeBlock } from './CodeBlock.tsx'
import 'katex/dist/katex.min.css'
import css from './MarkdownText.module.css'
const remarkPlugins = [remarkGfm, remarkMath]
const rehypePlugins = [rehypeKatex]
const streamingRemarkPlugins = [remarkGfm]
const settledRemarkPlugins = [remarkGfm, remarkMath]
const settledRehypePlugins = [rehypeKatex]
function sanitizeUrl(url: string): string {
try {
@@ -92,7 +93,7 @@ const streamingComponents = buildComponents(true)
/**
* Render untrusted assistant-authored Markdown as semantic React elements.
* @param props - Markdown source text preserved by the session projection;
* `streaming` renders fences plain (highlighting lands on the finalize swap);
* `streaming` renders fences and TeX plain (highlighting and KaTeX land on the finalize swap);
* `codeLabels` forwards localized copy-button labels to fence CodeBlocks —
* pass a reference-stable object (memoized per locale revision), because the
* component table memoizes on its identity and a fresh literal per render
@@ -113,8 +114,8 @@ export function MarkdownText({ text, streaming = false, codeLabels }: {
return (
<div className={css.markdown}>
<ReactMarkdown
remarkPlugins={remarkPlugins}
rehypePlugins={rehypePlugins}
remarkPlugins={streaming ? streamingRemarkPlugins : settledRemarkPlugins}
rehypePlugins={streaming ? undefined : settledRehypePlugins}
components={components}
urlTransform={safeUrl}
>
@@ -144,6 +144,21 @@ describe('MarkdownText', () => {
expect(container.querySelector('.katex-display annotation')?.textContent).toContain('\\frac{\\partial \\mathbf{u}}')
expect(container.querySelector('a')).toBeNull()
})
it('defers TeX rendering while streaming so incomplete formulas never flash KaTeX errors', () => {
const partial = '$$\n\\frac{\\partial \\mathbf{u}}{\\partial'
const complete = '$$\n\\frac{\\partial \\mathbf{u}}{\\partial t}\n$$'
const live = render(<MarkdownText text={partial} streaming />)
expect(live.container.querySelector('.katex')).toBeNull()
expect(live.container.querySelector('.katex-error')).toBeNull()
expect(live.container.textContent).toContain('\\frac{\\partial \\mathbf{u}}{\\partial')
live.rerender(<MarkdownText text={complete} />)
expect(live.container.querySelectorAll('.katex')).toHaveLength(1)
expect(live.container.querySelectorAll('.katex-display')).toHaveLength(1)
expect(live.container.querySelector('.katex-error')).toBeNull()
})
})
describe('JsonBlock', () => {