Skip to content

Commit e70aa66

Browse files
heavygeecursoragent
andcommitted
fix(web): accept signed viewBox values in mermaid lightbox normalize
HAPI Bot Minor (PR tiann#741): the viewBox regex only matched digits, dots, and spaces, so a valid viewBox with negative origin (e.g. '-8 -8 640 480') returned null. normalizeMermaidSvgForStandaloneDisplay then became a no-op and left width='100%', re-introducing the zero-sized lightbox render this PR is meant to fix for the affected diagrams. Switch to the bot's suggested regex (signed numbers, single or double quotes, comma or space separators) and reject NaN parts. Adds Vitest coverage for signed origins, single quotes, comma separators, the malformed/no-viewBox null paths, and an end-to-end normalize test that fails against the old regex. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 14cb8d0 commit e70aa66

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

web/src/components/assistant-ui/mermaid-diagram.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ export async function renderMermaidSvg(code: string, elementId: string, theme: '
9595
}
9696

9797
export function getMermaidSvgLayoutSize(svg: string): { width: number; height: number } | null {
98-
const viewBoxMatch = svg.match(/\bviewBox="([\d.\s]+)"/)
98+
const viewBoxMatch = svg.match(/\bviewBox=(['"])([^'"]+)\1/i)
9999
if (!viewBoxMatch) return null
100-
const parts = viewBoxMatch[1].trim().split(/\s+/).map(Number)
101-
if (parts.length < 4 || parts[2] <= 0 || parts[3] <= 0) return null
100+
const parts = viewBoxMatch[2].trim().split(/[\s,]+/).map(Number)
101+
if (parts.length < 4 || parts.some(Number.isNaN) || parts[2] <= 0 || parts[3] <= 0) return null
102102
return { width: parts[2], height: parts[3] }
103103
}
104104

web/src/components/assistant-ui/mermaid-svg-id.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
import { describe, expect, it } from 'vitest'
2-
import { normalizeMermaidSvgForStandaloneDisplay } from '@/components/assistant-ui/mermaid-diagram'
2+
import {
3+
getMermaidSvgLayoutSize,
4+
normalizeMermaidSvgForStandaloneDisplay,
5+
} from '@/components/assistant-ui/mermaid-diagram'
6+
7+
describe('getMermaidSvgLayoutSize', () => {
8+
it('reads simple unsigned viewBox', () => {
9+
expect(getMermaidSvgLayoutSize('<svg viewBox="0 0 200 80"></svg>')).toEqual({ width: 200, height: 80 })
10+
})
11+
12+
it('accepts signed origin values (negative offsets)', () => {
13+
expect(getMermaidSvgLayoutSize('<svg viewBox="-8 -8 640 480"></svg>')).toEqual({ width: 640, height: 480 })
14+
})
15+
16+
it('accepts single-quoted attribute and comma separators', () => {
17+
expect(getMermaidSvgLayoutSize("<svg viewBox='0,0,300,150'></svg>")).toEqual({ width: 300, height: 150 })
18+
})
19+
20+
it('rejects malformed viewBox (NaN, missing dim, zero size)', () => {
21+
expect(getMermaidSvgLayoutSize('<svg viewBox="0 0 NaN 100"></svg>')).toBeNull()
22+
expect(getMermaidSvgLayoutSize('<svg viewBox="0 0 200"></svg>')).toBeNull()
23+
expect(getMermaidSvgLayoutSize('<svg viewBox="0 0 0 100"></svg>')).toBeNull()
24+
})
25+
26+
it('returns null when no viewBox attribute exists', () => {
27+
expect(getMermaidSvgLayoutSize('<svg width="100"></svg>')).toBeNull()
28+
})
29+
})
330

431
describe('normalizeMermaidSvgForStandaloneDisplay', () => {
532
it('replaces width="100%" with explicit viewBox dimensions', () => {
@@ -10,4 +37,13 @@ describe('normalizeMermaidSvgForStandaloneDisplay', () => {
1037
expect(prepared).toContain('width:200px')
1138
expect(prepared).toContain('height:80px')
1239
})
40+
41+
it('normalizes width/height for SVGs with negative viewBox origins', () => {
42+
const svg = '<svg viewBox="-50 -50 800 600" width="100%" height="100%"><g/></svg>'
43+
const prepared = normalizeMermaidSvgForStandaloneDisplay(svg)
44+
45+
expect(prepared).not.toContain('width="100%"')
46+
expect(prepared).toContain('width="800"')
47+
expect(prepared).toContain('height="600"')
48+
})
1349
})

0 commit comments

Comments
 (0)