ASCII backend: an edge-less node in a target subgraph merges two subgraph frames and overwrites their titles

Author: theimpostorCreated Aug 17, 2026Updated Aug 29, 2026

Summary

In the ASCII backend, a node that has no edges causes its subgraph frame to be laid out on top of the preceding subgraph's frame. The two frames merge into one box and the two titles are written to the same row, so they overwrite each other character by character.

The SVG backend renders the same source correctly. Only renderMermaidASCII is affected.

Version

  • beautiful-mermaid 1.1.3 (latest on npm)
  • Bun 1.3.14, macOS arm64

Minimal reproduction

Three nodes, two subgraphs, one edge. b2 has no edges.

mermaid
flowchart TB
  subgraph a["Frontend tier"]
    a1["load balancer"]
  end
  subgraph b["Application tier"]
    b1["worker pool"]
    b2["api server"]
  end
  a1 --> b1

Steps to reproduce

bash
mkdir bm-repro && cd bm-repro
bun init -y
bun add [email protected]

Save as repro.ts:

typescript
import { renderMermaidASCII } from "beautiful-mermaid";

const source = `flowchart TB
  subgraph a["Frontend tier"]
    a1["load balancer"]
  end
  subgraph b["Application tier"]
    b1["worker pool"]
    b2["api server"]
  end
  a1 --> b1`;

console.log(renderMermaidASCII(source, { colorMode: "none" }));

Then:

bash
bun repro.ts

Actual output

┌───────────────────┬──────────────────┐
│   Frontendptieration tier            │
│                   │                  │
│                   │                  │
│ ┌───────────────┐ │   ┌────────────┐ │
│ │               │ │   │            │ │
│ │ load balancer │ │   │ api server │ │
│ │               │ │   │            │ │
│ └───────┬───────┘ │   └────────────┘ │
│         │         │                  │
├─────────┼─────────┘                  │
│         │                            │
│         │                            │
│         │                            │
│         │                            │
│         │                            │
│         │                            │
│         ▼                            │
│ ┌───────────────┐                    │
│ │               │                    │
│ │  worker pool  │                    │
│ │               │                    │
│ └───────────────┘                    │
│                                      │
└──────────────────────────────────────┘

Three defects are visible:

  1. The title row reads Frontendptieration tier. That is Frontend tier and Application tier written into the same cells.
  2. The two subgraphs share one outer frame instead of having one frame each.
  3. worker pool belongs to Application tier, but it is drawn in the left column, below load balancer, inside the region the Frontend tier title labels.

Expected output

Remove the edge-less node b2 and the same graph lays out correctly, with one frame per subgraph:

┌───────────────────┐
│   Frontend tier   │
│                   │
│                   │
│ ┌───────────────┐ │
│ │               │ │
│ │ load balancer │ │
│ │               │ │
│ └───────┬───────┘ │
│         │         │
└─────────┼─────────┘
          │
          │
          │
┌─────────┼─────────┐
│ Application tier  │
│         │         │
│         ▼         │
│ ┌───────────────┐ │
│ │               │ │
│ │  worker pool  │ │
│ │               │ │
│ └───────────────┘ │
│                   │
└───────────────────┘

b2 should appear as a second box inside the Application tier frame. Nothing else should change.

Trigger conditions

I tested each condition independently against 1.1.3.

Case Result
Edge-less node in the subgraph the edge points TO Reproduces
Edge-less node in the subgraph the edge comes FROM OK
Edge-less node outside every subgraph OK
Edge-less node with no subgraphs anywhere OK
Both subgraphs entirely edge-less OK
Every node connected OK

So the trigger is narrow: a subgraph that is the target of an edge, and that also contains at least one node with no edges.

The failure gets worse as the diagram grows. On a flowchart TB with 18 nodes, 18 edges and 4 subgraphs, all four subgraph titles are destroyed and nodes are drawn inside the wrong frames.

Impact

The output is not merely ugly. It is wrong: it shows nodes as belonging to subgraphs they are not in. A reader cannot tell that the diagram is mis-rendered, because the result is still a well-formed box drawing.

Note

The ASCII backend does not throw on this input, so a caller cannot detect the failure and fall back to showing the source. Raising an error, or leaving the frames un-merged, would both be preferable to silently wrong output.

Source: lukilabs/beautiful-mermaid