fix: contain markdown render crashes with an error boundary (#1586)

Streamdown bundles Mermaid and renders ```mermaid blocks itself; a
diagram it can't parse throws during render and, with no boundary,
white-screens the whole review page. Wrap the markdown render so any
crash falls back to the raw text instead.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Johannes du Plessis 2026-06-22 12:54:05 -07:00 • committed by GitHub
parent f54fc154ca
commit 609e5551c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,4 +1,4 @@
import { memo, useMemo } from "react";
import { Component, memo, useMemo } from "react";
import { Streamdown } from "streamdown";
import type { ReactNode } from "react";
import "streamdown/styles.css";
@ -76,6 +76,46 @@ const STREAMDOWN_COMPONENTS = {
const SHIKI_THEME: ["github-light", "github-dark"] = ["github-light", "github-dark"];
interface BoundaryProps {
content: string;
children: ReactNode;
}
interface BoundaryState {
failed: boolean;
key: string;
}
// Streamdown bundles Mermaid and renders ```mermaid blocks itself; a diagram it
// can't parse throws during render and, with no boundary, white-screens the
// whole page. Contain it and fall back to the raw markdown text.
class MarkdownErrorBoundary extends Component<BoundaryProps, BoundaryState> {
state: BoundaryState = { failed: false, key: this.props.content };
static getDerivedStateFromError(): Partial<BoundaryState> {
return { failed: true };
}
static getDerivedStateFromProps(
props: BoundaryProps,
state: BoundaryState
): Partial<BoundaryState> | null {
if (props.content !== state.key) return { failed: false, key: props.content };
return null;
}
render(): ReactNode {
if (this.state.failed) {
return (
<pre className="whitespace-pre-wrap break-words [overflow-wrap:anywhere] font-sans text-[color:var(--ui-text)]">
{this.props.content}
</pre>
);
}
return this.props.children;
}
}
export const Markdown = memo(function Markdown({
content,
isLive = false,
@ -99,17 +139,19 @@ export const Markdown = memo(function Markdown({
return (
<div className="min-w-0 max-w-full text-[13px] leading-6 break-words [overflow-wrap:anywhere] [&_.streamdown]:text-[color:var(--ui-text)]">
<Streamdown
mode={isLive ? "streaming" : "static"}
parseIncompleteMarkdown={isLive}
isAnimating={isLive}
animated={isLive ? STREAMDOWN_ANIMATED : false}
shikiTheme={SHIKI_THEME}
className="streamdown-agent min-w-0 max-w-full"
components={components}
>
{content}
</Streamdown>
<MarkdownErrorBoundary content={content}>
<Streamdown
mode={isLive ? "streaming" : "static"}
parseIncompleteMarkdown={isLive}
isAnimating={isLive}
animated={isLive ? STREAMDOWN_ANIMATED : false}
shikiTheme={SHIKI_THEME}
className="streamdown-agent min-w-0 max-w-full"
components={components}
>
{content}
</Streamdown>
</MarkdownErrorBoundary>
</div>
);
});