diff --git a/apps/desktop/src/components/assistant-ui/embeds/mermaid-embed.test.tsx b/apps/desktop/src/components/assistant-ui/embeds/mermaid-embed.test.tsx
index d4ce73065c..ade733e3df 100644
--- a/apps/desktop/src/components/assistant-ui/embeds/mermaid-embed.test.tsx
+++ b/apps/desktop/src/components/assistant-ui/embeds/mermaid-embed.test.tsx
@@ -47,21 +47,23 @@ describe('MermaidRenderer', () => {
expect(decodeURIComponent(images[0]?.src.split(',')[1] ?? '')).toContain('marker-end="url(#arrow)"')
})
- it('repairs a label mermaid returned with an open , so the image stays loadable', async () => {
- // `A[x y]` comes back from mermaid as ` ` — malformed XML, so the
- // data: URI fails to decode and the message shows a broken image with the
- // alt text instead of the diagram (#133089).
+ it('repairs a label mermaid returned as HTML, so the image stays loadable', async () => {
+ // `A["a b c"]` comes back from mermaid with an open ` ` and an
+ // ` ` entity — malformed XML, so the data: URI fails to decode and the
+ // message shows a broken image with the alt text instead of the diagram
+ // (#133089).
renderMermaid.mockResolvedValueOnce({
- svg: ''
+ svg: ''
})
- const { container } = render()
+ const { container } = render()
await waitFor(() => expect(container.querySelectorAll('img')).toHaveLength(1))
const src = decodeURIComponent([...container.querySelectorAll('img')][0]?.src.split(',')[1] ?? '')
+ const doc = new DOMParser().parseFromString(src, 'image/svg+xml')
- expect(src).toContain(' ')
- expect(new DOMParser().parseFromString(src, 'image/svg+xml').documentElement.tagName).toBe('svg')
+ expect(doc.documentElement.tagName).toBe('svg')
+ expect(doc.querySelector('p')?.textContent).toBe('ab c')
})
})
diff --git a/apps/desktop/src/components/assistant-ui/embeds/mermaid-embed.tsx b/apps/desktop/src/components/assistant-ui/embeds/mermaid-embed.tsx
index 2dc7a93491..8b5857638a 100644
--- a/apps/desktop/src/components/assistant-ui/embeds/mermaid-embed.tsx
+++ b/apps/desktop/src/components/assistant-ui/embeds/mermaid-embed.tsx
@@ -36,9 +36,10 @@ const renderCache = createMermaidRenderCache({
const id = `mmd-${Math.random().toString(36).slice(2)}`
const result = await mermaid.render(id, code)
- // Mermaid's markup targets inline HTML, so a ` ` in a label reaches us
- // as an open ` `; close void tags before the size pass and before the
- // data: URI below, or the diagram decodes to a broken image (#133089).
+ // Mermaid's markup targets inline HTML, so a label's ` ` reaches us as
+ // an open ` ` and a non-breaking space as ` `; make it XML before
+ // the size pass and the data: URI below, or the diagram decodes to a
+ // broken image (#133089).
return normalizeSvgSize(xmlWellFormedSvg(result.svg))
},
// Defer until the source fallback has had a frame to paint, so the mermaid
diff --git a/apps/desktop/src/lib/svg-image.test.ts b/apps/desktop/src/lib/svg-image.test.ts
index 4197f39290..87be49ad1d 100644
--- a/apps/desktop/src/lib/svg-image.test.ts
+++ b/apps/desktop/src/lib/svg-image.test.ts
@@ -65,40 +65,27 @@ describe('normalizeSvgSize', () => {
})
})
-// A mermaid label keeps HTML void tags open: ` ` in the source comes back
-// from `mermaid.render()` as ` `. That makes the string malformed XML, so
-// every strict `image/svg+xml` parse below degrades — and Blink fails the same
-// way when the string is handed to an as a data: URI, which is how a
-// diagram silently stops rendering (broken image, no error anywhere).
+// Real mermaid 11.16 label island for `A["a b c"]` (verified against
+// the installed package): the HTML serialisation leaves ` ` open and writes
+// the non-breaking space as ` `, and neither is well-formed XML. Every
+// strict `image/svg+xml` parse degrades on it — and Blink fails the same way
+// when the string is handed to an as a data: URI, which is how a diagram
+// silently stops rendering (broken image, no error anywhere).
+const MERMAID_LABEL_SVG = ``
+
describe('xmlWellFormedSvg', () => {
- const rootOf = (svg: string) => new DOMParser().parseFromString(svg, 'image/svg+xml').documentElement.tagName
+ const parseXml = (svg: string) => new DOMParser().parseFromString(svg, 'image/svg+xml')
- it('closes a void tag mermaid left open inside a label', () => {
- expect(xmlWellFormedSvg(' x')).toBe(' x')
+ it('makes a mermaid label with and parse as an