diff --git a/apps/web/components/shared/mermaid-utils.test.ts b/apps/web/components/shared/mermaid-utils.test.ts index ae5c1008e4..1bbda02a49 100644 --- a/apps/web/components/shared/mermaid-utils.test.ts +++ b/apps/web/components/shared/mermaid-utils.test.ts @@ -80,4 +80,29 @@ describe("sanitizeMermaidCode", () => { const out = sanitizeMermaidCode(input); expect(out).toContain(`B["/api/x"]`); }); + + it("does not match an edge label that spans a newline", () => { + const input = `A -->|open\nB | C`; + expect(sanitizeMermaidCode(input)).toBe(input); + }); + + it("does not match a bracket label that spans a newline", () => { + const input = `A[open\nB]`; + expect(sanitizeMermaidCode(input)).toBe(input); + }); + + it("does not match a parenthesis label that spans a newline", () => { + const input = `A(open\n/path)`; + expect(sanitizeMermaidCode(input)).toBe(input); + }); + + it("does not quote ER diagram cardinality bars as flowchart edge labels", () => { + const input = [ + "erDiagram", + "workspaces ||--o{ workflows : owns", + "workflows ||--o{ workflow_steps : contains", + ].join("\n"); + + expect(sanitizeMermaidCode(input)).toBe(input); + }); }); diff --git a/apps/web/components/shared/mermaid-utils.ts b/apps/web/components/shared/mermaid-utils.ts index b74113fe2c..940d47a45a 100644 --- a/apps/web/components/shared/mermaid-utils.ts +++ b/apps/web/components/shared/mermaid-utils.ts @@ -85,7 +85,7 @@ const inAnyRange = (offset: number, ranges: Array<[number, number]>): boolean => export function sanitizeMermaidCode(code: string): string { // Quote node labels: [text] or [[text]] etc. Match brackets that aren't already quoted. let quoted = findQuotedRanges(code); - let result = code.replace(/(\[+)([^\]"]+?)(\]+)/g, (match, open, text, close, offset) => { + let result = code.replace(/(\[+)([^\]\n"]+?)(\]+)/g, (match, open, text, close, offset) => { if (inAnyRange(offset, quoted)) return match; if (BRACKET_TRIGGER_RE.test(text)) { return `${open}"${text}"${close}`; @@ -95,7 +95,7 @@ export function sanitizeMermaidCode(code: string): string { // Quote edge labels: |text| quoted = findQuotedRanges(result); - result = result.replace(/\|([^|"]+?)\|/g, (match, text, offset) => { + result = result.replace(/\|([^|\n"]+?)\|/g, (match, text, offset) => { if (inAnyRange(offset, quoted)) return match; if (SPECIAL_CHARS_RE.test(text)) { return `|"${text}"|`; @@ -105,7 +105,7 @@ export function sanitizeMermaidCode(code: string): string { // Quote parentheses labels: (text) for stadium/circle nodes quoted = findQuotedRanges(result); - result = result.replace(/(\(+)([^)"]+?)(\)+)/g, (match, open, text, close, offset) => { + result = result.replace(/(\(+)([^)\n"]+?)(\)+)/g, (match, open, text, close, offset) => { if (inAnyRange(offset, quoted)) return match; // Skip if it looks like a subgraph or keyword if (/^(subgraph|end|graph|flowchart|sequenceDiagram)\b/.test(text.trim())) {