From 64ba63c577808c2e806348cdd4adb4663ed85d71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9C=8D=E7=84=B6?= Date: Thu, 12 Mar 2026 10:25:57 +0800 Subject: [PATCH] fix(web-ui): reset Markdown error boundary when content changes - Add componentDidUpdate to MarkdownErrorBoundary to clear hasError when fallbackContent (markdownContent) changes - Prevents remark-gfm transformGfmAutolinkLiterals RegExp errors from permanently showing fallback; new messages get a fresh render attempt Made-with: Cursor --- .../components/Markdown/Markdown.tsx | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/web-ui/src/component-library/components/Markdown/Markdown.tsx b/src/web-ui/src/component-library/components/Markdown/Markdown.tsx index 89adf32c..5837c677 100644 --- a/src/web-ui/src/component-library/components/Markdown/Markdown.tsx +++ b/src/web-ui/src/component-library/components/Markdown/Markdown.tsx @@ -3,7 +3,7 @@ * Used to render Markdown-formatted text */ -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo, useCallback, Component, type ReactNode } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; @@ -21,6 +21,39 @@ import './Markdown.scss'; const log = createLogger('Markdown'); const COMPUTER_LINK_PREFIX = 'computer://'; + +/** Catches render errors from react-markdown/remark-gfm (e.g. RegExp in transformGfmAutolinkLiterals) and shows plain text fallback. */ +class MarkdownErrorBoundary extends Component< + { children: ReactNode; fallbackContent: string }, + { hasError: boolean } +> { + state = { hasError: false }; + + static getDerivedStateFromError() { + return { hasError: true }; + } + + componentDidCatch(error: Error) { + log.error('Markdown render error, showing plain text fallback', { message: error.message }); + } + + componentDidUpdate(prevProps: { fallbackContent: string }) { + if (prevProps.fallbackContent !== this.props.fallbackContent && this.state.hasError) { + this.setState({ hasError: false }); + } + } + + render() { + if (this.state.hasError) { + return ( +
+ {this.props.fallbackContent} +
+ ); + } + return this.props.children; + } +} const FILE_LINK_PREFIX = 'file://'; const WORKSPACE_FOLDER_PLACEHOLDER = '{{workspaceFolder}}'; @@ -514,14 +547,18 @@ export const Markdown = React.memo(({ isLight ]); + const wrapperClassName = `markdown-renderer ${className} ${isStreaming && contentStr ? 'markdown-renderer--streaming' : ''}`.trim(); + return ( -
- - {markdownContent} - +
+ + + {markdownContent} + + {reproductionSteps && !isStreaming && (