Et tu, D2?
making sure D2 always produces exact same diagrams
What is D2?
D2 (Declarative Diagramming) is a "modern language that turns text to diagrams". If you expect your architecture diagrams to evolve over time, you want to have them in a semantic, and preferably textual format that is git-friendly, and can easily be edited by humans and LLM agents alike.
Consider this diagram below: Imagine I had created it as a PNG. What if I later realize that Azure monitor is not being used? I'll have to painstakingly edit the image. Instead, if I had used D2 to specify this diagram and generate the PNG, it would've been a matter of changing a couple of lines in the specification and regenerating the PNG.

That's why, here at Oodle, it's been our choice for architecture diagrams. Here I'm jotting down my quick hack for getting around the fact that D2 files are not fully self-describing: the same .d2 file can generate different-looking diagrams!
But D2 is already self describing...
I know right, I thought so too! And then, I ran into this: same .d2 source as the diagram we just saw, but look at the spaghetti!

This is the story of these two diagrams, but with toy examples for brevity.
I got Codex to generate and render a d2 diagram.
direction: down
ingest: Ingest
process: Process
store: Store
ingest -> process
process -> store
ingest -> store
I was happy, and I moved on.
A few days later I wanted to make some changes to the diagram and I was working with the fresh Codex session. This time from the same d2 diagram, it rendered this abomination:

I saw that Codex had just run the d2 command without any flags. Lucky me, I had kept the older transcripts. Turns out, in the earlier session, it was a bit more sophisticated:
d2 --layout elk \
--elk-nodeNodeBetweenLayers 30 \
--elk-edgeNodeBetweenLayers 15 \
--elk-nodeSelfLoop 45 \
--elk-padding "[top=20,left=20,bottom=20,right=20]"In one session, Codex had decided to generate diagrams by providing extra command-line arguments, in another one, it had gotten lazy. 😤
Quasi-Self-Description
I wanted all the logic that is required to generate this D2 diagram to be contained within the file, so I put the whole command in a comment:
# Render using
# d2 --layout elk \
# --elk-nodeNodeBetweenLayers 30 \
# --elk-edgeNodeBetweenLayers 15 \
# --elk-nodeSelfLoop 45 \
# --elk-padding "[top=20,left=20,bottom=20,right=20]"
direction: down
ingest: Ingest
process: Process
store: Store
ingest -> process
process -> store
ingest -> storeNow I can hope that Codex reads the comment before rendering. 🤞🏼
As a human it is still annoying though. I either need to take it out in a script or copy this command from the file before running it. I don't like the script option. It needs me to look into two places to fully understand the diagram.
Truly Self-Describing
The act of putting the command as a comment at the top was a light bulb moment for me: "Hey, hold on! Why not use a shebang?"
Ta da! Now you get a fully self-describing D2 file that is executable.
#!/usr/bin/env -S d2 --layout elk --elk-nodeNodeBetweenLayers 30 --elk-edgeNodeBetweenLayers 15 --elk-modeSelfLoop 45 --elk-padding [top=20,left=20,bottom=20,right=20]
direction: down
ingest: Ingest
process: Process
store: Store
ingest -> process
process -> store
ingest -> storeDoesn't D2 already allow you to specify this in-file?
Great question! It should! Turns out not all CLI flags have an equivalent in-file configuration. There is a PR (#2608) that allows much more in-file customization of layout, but it is still open. Till it is merged, shebang it is!