refactor(desktop): route preview-pane mermaid fences through shared embeds registry

Drop the duplicate mermaid-block.tsx (own mermaid.initialize + render path,
theme frozen at first load) and wire preview-file.tsx's MarkdownCode through
the existing RichCodeBlock registry from #52935 instead. One mermaid init
path, theme-flip re-init, Zoomable + copy-as-PNG, RichBoundary error
fallback — and the preview pane gets svg fences for free. Shiki block stays
as the fallback for all other languages.
This commit is contained in:
teknium1 2026-07-07 02:13:58 -07:00 committed by Teknium
parent c0adfd4a67
commit 7ff86f4458
2 changed files with 9 additions and 100 deletions

View file

@ -6,7 +6,7 @@ import type {
MouseEvent as ReactMouseEvent,
ReactNode
} from 'react'
import { Fragment, lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import ShikiHighlighter from 'react-shiki'
import { Streamdown } from 'streamdown'
@ -14,6 +14,7 @@ import { requestComposerFocus, requestComposerInsertRefs } from '@/app/chat/comp
import { droppedFileInlineRef } from '@/app/chat/composer/inline-refs'
import { HERMES_PATHS_MIME } from '@/app/chat/hooks/use-composer-actions'
import { isAddSelectionShortcut } from '@/app/right-sidebar/terminal/selection'
import { RichCodeBlock } from '@/components/assistant-ui/embeds'
import { CodeEditor } from '@/components/chat/code-editor'
import { FileDiffPanel } from '@/components/chat/diff-lines'
import { chunkTextLines, useFixedRowWindow } from '@/components/chat/fixed-row-window'
@ -34,10 +35,6 @@ import { setPreviewDirty } from '@/store/preview-edit'
import { $currentCwd } from '@/store/session'
import { notifyWorkspaceChanged } from '@/store/workspace-events'
const MermaidDiagram = lazy(() =>
import('@/components/chat/mermaid-block').then(m => ({ default: m.MermaidBlock }))
)
const SHIKI_THEME = { dark: 'github-dark-default', light: 'github-light-default' } as const
const TEXT_PREVIEW_MAX_BYTES = 512 * 1024
const SOURCE_CHUNK_LINES = 200
@ -295,17 +292,9 @@ function MarkdownCode({ className, children, ...props }: ComponentProps<'code'>)
)
}
if (language === 'mermaid') {
return (
<Suspense
fallback={<div className="my-3 text-xs text-muted-foreground">Loading diagram</div>}
>
<MermaidDiagram chart={String(children).replace(/\n$/, '')} />
</Suspense>
)
}
const code = String(children).replace(/\n$/, '')
return (
const highlighted = (
<ShikiHighlighter
addDefaultStyles={false}
as="div"
@ -315,9 +304,13 @@ function MarkdownCode({ className, children, ...props }: ComponentProps<'code'>)
showLanguage={false}
theme={SHIKI_THEME}
>
{String(children).replace(/\n$/, '')}
{code}
</ShikiHighlighter>
)
// ```mermaid / ```svg fences route to the shared lazy renderers (same
// registry the chat transcript uses); everything else stays on Shiki.
return <RichCodeBlock code={code} fallback={highlighted} language={language} />
}
const MARKDOWN_COMPONENTS = {

View file

@ -1,84 +0,0 @@
import { useEffect, useRef, useState } from 'react'
let mermaidPromise: Promise<typeof import('mermaid')> | null = null
function loadMermaid() {
if (!mermaidPromise) {
mermaidPromise = import('mermaid').then(async mod => {
const m = mod.default
m.initialize({
securityLevel: 'strict',
startOnLoad: false,
theme: document.documentElement.classList.contains('dark') ? 'dark' : 'default'
})
return mod
})
}
return mermaidPromise
}
interface MermaidBlockProps {
chart: string
}
export function MermaidBlock({ chart }: MermaidBlockProps) {
const containerRef = useRef<HTMLDivElement>(null)
const [error, setError] = useState<string | null>(null)
const [svg, setSvg] = useState<string | null>(null)
useEffect(() => {
let cancelled = false
async function render() {
try {
const { default: mermaid } = await loadMermaid()
const id = `mermaid-${Math.random().toString(36).slice(2, 10)}`
const { svg: rendered } = await mermaid.render(id, chart)
if (!cancelled) {
setSvg(rendered)
setError(null)
}
} catch (err) {
if (!cancelled) {
setError(err instanceof Error ? err.message : String(err))
setSvg(null)
}
}
}
void render()
return () => {
cancelled = true
}
}, [chart])
if (error) {
return (
<div className="my-3 rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground">
<div className="mb-1 font-medium text-foreground">Mermaid diagram error</div>
<pre className="whitespace-pre-wrap break-words text-[0.75rem]">{error}</pre>
<details className="mt-2">
<summary className="cursor-pointer text-muted-foreground/70 hover:text-foreground">View source</summary>
<pre className="mt-1 whitespace-pre-wrap break-words text-[0.75rem]">{chart}</pre>
</details>
</div>
)
}
if (!svg) {
return <div className="my-3 text-xs text-muted-foreground">Rendering diagram</div>
}
return (
<div
ref={containerRef}
className="my-3 overflow-auto rounded-md border border-border bg-background p-3 [&_svg]:max-w-full"
dangerouslySetInnerHTML={{ __html: svg }}
/>
)
}