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:
- Relax rel regex — make the 4th arg optional; add
BiRel / Rel_Back / Rel_U/D/L/R. Small, isolated change.
- Add
*_Boundary types — extend isValidEntityType and emit as clusters in layoutData2mx.
- Broaden
layoutData2mx switch — add all 17 missing types with appropriate shape classes under mx/c4/.
- 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.
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-argRel(...)calls survive the round trip.Minimal repro
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:
src/puml/RelParser.mts:26/Rel\(([^,]+),\s*([^,]+),\s*\"([^\"]+)\",\s*\"([^\"]+)\"\)/grequires exactly 4 args. 3-argRel(src, tgt, \"label\")doesn't match. Also no support forBiRel,Rel_Back,Rel_U/D/L/R,Rel_Neighbor.src/puml/EntityParser.mts:5-27isValidEntityTypeomitsSystem_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 throughparseBlockreturning null.src/catalyst.mts:16-28, 40-49layoutData2mxswitch handles onlySystem,Container,Component. All 17 other valid types returned byEntityParser(Person,Person_Ext,System_Ext,SystemDb,SystemQueue,ContainerDb,Container_Ext,*_Ext,*Db,*Queuevariants) hitdefault: breakand vanish.src/puml/EntityParser.mts:36.split(',')on the block's argument list breaks on descriptions containing commas. Minor — hit less often than the above.src/mx/Mx.mts<diagram>with noid/nameattributes.drawio-exportand some drawio ecosystem tools reject the XML withmissing 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:
BiRel/Rel_Back/Rel_U/D/L/R. Small, isolated change.*_Boundarytypes — extendisValidEntityTypeand emit as clusters inlayoutData2mx.layoutData2mxswitch — add all 17 missing types with appropriate shape classes undermx/c4/.<diagram id=\"...\" name=\"...\">— trivial fix; unlocksdrawio-exportanddrawio-desktopheadless flows.Each is self-contained and won't interact with the others. I'd file them as four separate PRs against
mainso 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
puml2drawioaround catalyst and hit all of these when running against real C4 diagrams — happy to contribute fixes upstream rather than forking.