Skip to content

[Bug]: Multiple mermaid diagrams overlap when rendered in markdown preview #3191

@babywait

Description

@babywait

Bug Description

When a markdown file contains multiple mermaid code blocks, the rendered diagrams overlap/stack on top of each other instead of being displayed independently.

Steps to Reproduce

Create a markdown file with multiple mermaid code blocks and preview it in Wave:

# Wave Mermaid Multiple Diagrams Repro

## Diagram 1 - Flowchart

```mermaid
flowchart TD
    A[Start] --> B{Decision}
    B -->|Yes| C[Action 1]
    B -->|No| D[Action 2]
    C --> E[End]
    D --> E
```

## Diagram 2 - Sequence

```mermaid
sequenceDiagram
    participant Alice
    participant Bob
    Alice->>Bob: Hello
    Bob-->>Alice: Hi back
    Alice->>Bob: How are you?
    Bob-->>Alice: Good thanks
```

## Diagram 3 - Flowchart

```mermaid
flowchart LR
    X[Input] --> Y[Process]
    Y --> Z[Output]
    Z --> W{Valid?}
    W -->|Yes| Done[Done]
    W -->|No| X
```

Expected Behavior

Each mermaid diagram should render independently in its own section, with no visual overlap.

Actual Behavior

The diagrams overlap and stack on top of each other. Elements from Diagram 3 (flowchart LR) are rendered on top of Diagram 2 (sequence diagram). A single mermaid block renders fine — the issue only appears with 2+ diagrams.

Root Cause Analysis

After reading the source code (frontend/app/element/markdown.tsx), the issue appears to be a race condition in concurrent mermaid.run() calls:

  1. Each <Mermaid> component calls mermaid.run() in its own useEffect, sharing a single module-level mermaidInstance
  2. When React renders multiple Mermaid components, all useEffect hooks fire in the same microtask batch — there is no serialization/mutex
  3. mermaid.run() internally uses Date.now() for SVG element IDs (when deterministicIds is not set). Concurrent calls within the same millisecond generate identical IDs, causing SVG elements to reference or overwrite each other
  4. The useEffect also lacks a cleanup function, so re-renders during an in-flight mermaid.run() can cause stale DOM mutations

Suggested Fix

  • Serialize mermaid.run() calls with a promise queue/mutex so they never execute concurrently
  • Consider enabling deterministicIds: true in mermaid configuration
  • Add a cleanup function to the useEffect to handle component unmount during rendering

Environment

  • Wave version: 0.14.4 (202603271721)
  • OS: macOS 14.5 (Darwin 23.4.0)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions