What happens
A self-referential relationship (entity: X declared on X) is emitted as an ordinary
erDiagram edge:
Record ||--o| Record : "the record this one supersedes"
Mermaid's default (dagre) ER layout has no self-loop handling. It draws the edge as an
enormous unbounded arc that sweeps down and around, often several times the height of the
entity box, and parks the label far away from the line it belongs to. On a real model with
two self-relationships the arcs dominate the canvas, push every other entity into a corner,
and one of them ran off the edge of the rendered SVG entirely.
This is a Mermaid limitation, not a modelith correctness bug — but modelith chooses the
representation, and this one is unusable on the renderer everyone actually views the output
in (GitHub).
Repro
repro.modelith.yaml:
# yaml-language-server: $schema=https://modelith.sh/schema/domain-model/v1.json
kind: DomainModel
version: v1
title: Self-relationship repro
description: >
Minimal model with one self-referential relationship.
entities:
Record:
definition: A dated decision record.
attributes:
- name: id
type: string
relationships:
- entity: Record
cardinality: "1:0..1"
role: "the record this one supersedes"
- entity: Note
cardinality: "1:n"
ownership: owned
Note:
definition: A note attached to a record.
attributes:
- name: body
type: string
modelith lint repro.modelith.yaml # clean (completeness warnings only)
modelith render repro.modelith.yaml # then view the .md on GitHub, or:
npx -y @mermaid-js/mermaid-cli@11 -i diagram.mmd -o out.png
Verified against @mermaid-js/mermaid-cli@11.16.0 and GitHub's own Markdown renderer.
The loop is independent of the label — : "" and a one-word label produce the same arc —
so there is no label-side workaround.
Why it was never caught
examples/example.modelith.yaml has no self-referential relationship, so the golden
fixture never exercises this path.
Options
-
Render the self-relationship inside the entity's attribute block instead of as an
edge. Mermaid accepts a PascalCase type there, so:
Record {
Record self "0..1 — the record this one supersedes"
}
Tested on the real model: both runaway arcs disappear, the diagram becomes compact, and
the relationship stays visible. This is my recommendation. It does mean the entity block
is no longer always {} — internal/render/mermaid/mermaid.go currently emits %s {}
unconditionally and documents that attributes are deliberately omitted, so the comment
there needs updating too.
-
Omit self-relationships from the diagram. They are already listed in the Markdown
per-entity relationship section, so nothing is lost in the rendered .md — but it is
lost for anyone consuming the Mermaid alone.
-
layout: elk frontmatter. ELK lays these out perfectly (verified). Rejected:
GitHub does not bundle @mermaid-js/layout-elk, so the directive is inert exactly where
the output is read.
Whatever we pick, add a self-referential relationship to examples/example.modelith.yaml
so the golden test covers it.
What happens
A self-referential relationship (
entity: Xdeclared onX) is emitted as an ordinaryerDiagram edge:
Mermaid's default (dagre) ER layout has no self-loop handling. It draws the edge as an
enormous unbounded arc that sweeps down and around, often several times the height of the
entity box, and parks the label far away from the line it belongs to. On a real model with
two self-relationships the arcs dominate the canvas, push every other entity into a corner,
and one of them ran off the edge of the rendered SVG entirely.
This is a Mermaid limitation, not a modelith correctness bug — but modelith chooses the
representation, and this one is unusable on the renderer everyone actually views the output
in (GitHub).
Repro
repro.modelith.yaml:Verified against
@mermaid-js/mermaid-cli@11.16.0and GitHub's own Markdown renderer.The loop is independent of the label —
: ""and a one-word label produce the same arc —so there is no label-side workaround.
Why it was never caught
examples/example.modelith.yamlhas no self-referential relationship, so the goldenfixture never exercises this path.
Options
Render the self-relationship inside the entity's attribute block instead of as an
edge. Mermaid accepts a PascalCase type there, so:
Tested on the real model: both runaway arcs disappear, the diagram becomes compact, and
the relationship stays visible. This is my recommendation. It does mean the entity block
is no longer always
{}—internal/render/mermaid/mermaid.gocurrently emits%s {}unconditionally and documents that attributes are deliberately omitted, so the comment
there needs updating too.
Omit self-relationships from the diagram. They are already listed in the Markdown
per-entity relationship section, so nothing is lost in the rendered
.md— but it islost for anyone consuming the Mermaid alone.
layout: elkfrontmatter. ELK lays these out perfectly (verified). Rejected:GitHub does not bundle
@mermaid-js/layout-elk, so the directive is inert exactly wherethe output is read.
Whatever we pick, add a self-referential relationship to
examples/example.modelith.yamlso the golden test covers it.