Skip to content

C4 export gaps: Persons, System_Ext, boundaries, and 3-arg Rel() are silently dropped #554

@AndriyKalashnykov

Description

@AndriyKalashnykov

Summary

Converting standard C4-PlantUML diagrams through Catalyst.convert() produces drawio output that's missing most shapes and most relationships. Only a subset of element types and only 4-arg Rel(...) calls survive the round trip.

Minimal repro

@startuml c4-context
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/v2.10.0/C4_Context.puml

Person(dev, "Developer", "Runs make targets")

System_Boundary(host, "Workstation") {
  System(kind, "kind-cluster", "Local KinD stack")
  System_Ext(docker, "Docker", "Container runtime")
}

System_Ext(registries, "Container registries", "Docker Hub, GHCR")

Rel(dev, kind, "make kind-up / kind-down", "shell + kubectl")
Rel(kind, docker, "Runs nodes as containers")
Rel(kind, registries, "Pulls workload images")
@enduml

Expected (what plantuml renders): 1 Person + 1 System + 2 System_Ext + 1 System_Boundary + 3 Rels = 7 shapes + 3 edges.

Actual (what Catalyst.convert() produces): 1 System (kind) + 1 edge (dev → kind). Everything else is silently dropped.

Root causes in source

I traced each gap to a specific line:

# File Issue
1 src/puml/RelParser.mts:26 Regex /Rel\(([^,]+),\s*([^,]+),\s*\"([^\"]+)\",\s*\"([^\"]+)\"\)/g requires exactly 4 args. 3-arg Rel(src, tgt, \"label\") doesn't match. Also no support for BiRel, Rel_Back, Rel_U/D/L/R, Rel_Neighbor.
2 src/puml/EntityParser.mts:5-27 isValidEntityType omits System_Boundary, Container_Boundary, Enterprise_Boundary. Boundaries are parsed as stack frames (via the { branch at :88-95) but never emitted, and child entities lose their parent linkage through parseBlock returning null.
3 src/catalyst.mts:16-28, 40-49 layoutData2mx switch handles only System, Container, Component. All 17 other valid types returned by EntityParser (Person, Person_Ext, System_Ext, SystemDb, SystemQueue, ContainerDb, Container_Ext, *_Ext, *Db, *Queue variants) hit default: break and vanish.
4 src/puml/EntityParser.mts:36 Naive .split(',') on the block's argument list breaks on descriptions containing commas. Minor — hit less often than the above.
5 src/mx/Mx.mts Output emits <diagram> with no id / name attributes. drawio-export and some drawio ecosystem tools reject the XML with missing field '@id'; drawio Desktop is more lenient but still imports it as an unnamed page.

Impact

Anyone authoring C4 diagrams with standard C4-PlantUML primitives (which is most real-world usage — Persons and System_Ext are core to Context diagrams, boundaries are core to Container/Component diagrams) gets heavily degraded output with no warning. Layout uses dagre correctly for the elements that survive, so the output looks plausible until you realise half your diagram is gone.

Proposal

Happy to send focused PRs for each gap:

  1. Relax rel regex — make the 4th arg optional; add BiRel / Rel_Back / Rel_U/D/L/R. Small, isolated change.
  2. Add *_Boundary types — extend isValidEntityType and emit as clusters in layoutData2mx.
  3. Broaden layoutData2mx switch — add all 17 missing types with appropriate shape classes under mx/c4/.
  4. Emit <diagram id=\"...\" name=\"...\"> — trivial fix; unlocks drawio-export and drawio-desktop headless flows.

Each is self-contained and won't interact with the others. I'd file them as four separate PRs against main so they can be reviewed / merged independently. Confirm you'd like these and I'll start landing them.

Context

Filing this as a single tracking issue rather than 5 separate ones so the full picture is visible. I maintain a Docker-wrapper CLI puml2drawio around catalyst and hit all of these when running against real C4 diagrams — happy to contribute fixes upstream rather than forking.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions