Sharing & positions
A diagram is its source text plus the positions of its nodes. The editor keeps both in the URL, so a link is a complete copy of the diagram. No server is involved unless you choose to save to HashBin.
The diagram URL
As you type or drag, the editor rewrites the part of its URL after #:
https://curtcox.github.io/modl/#h1=<base64url of a JSON state object>
The state object has two fields:
{
"src": "a -> b\nb square red\n",
"pos": { "a": [160, 200], "b": [460, 200] }
}
src: the modl source, exactly as typed.pos: node id to[x, y], in canvas pixels. It may be empty or leave nodes out.
The JSON is UTF-8 encoded and then base64url encoded (- and _ instead of + and
/, with no = padding), and prefixed with h1=. Every example link in these docs is
built this way. Copy URL in the editor copies the current link, and Commit snapshot adds it to the
browser history so Back and Forward step between versions.
Positions and layout
Coordinates are measured in pixels from the top-left corner of the diagram canvas, before any panning or zooming. The editor rounds them to whole numbers when you drag. Zoom, pan and the current view (Source, Diagram, Help, Split) are not saved.
Nodes without a position are placed automatically, spread evenly around a circle in the middle of the canvas, in sorted id order. Their placement depends on the window size when the diagram opens. Positions for ids that no longer exist in the source are dropped.
start square teal Start
build square blue Build
test square blue Test
ship square purple Ship
start -> build -> test -> ship
Open in editor
start square teal Start
build square blue Build
test square blue Test
ship square purple Ship
start -> build -> test -> ship
Open in editor
To lay out a diagram by hand, open it, drag the nodes where you want them, and copy the URL. The positions are now part of the link.
Making links in code
Since the format is plain JSON and base64url, links are easy to generate. In JavaScript:
function modlUrl(src, pos = {}) {
const bytes = new TextEncoder().encode(JSON.stringify({ src, pos }));
const b64 = btoa(String.fromCharCode(...bytes));
return "https://curtcox.github.io/modl/#h1=" +
b64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
}
modlUrl("a -> b\n");
modlUrl("a -> b\n", { a: [160, 200], b: [460, 200] });
In Python:
import base64, json
def modl_url(src, pos=None):
data = json.dumps({"src": src, "pos": pos or {}}).encode("utf-8")
return "https://curtcox.github.io/modl/#h1=" + base64.urlsafe_b64encode(data).decode().rstrip("=")
From a shell, for a file diagram.modl (needs jq):
echo "https://curtcox.github.io/modl/#h1=$(jq -Rsc '{src: ., pos: {}}' diagram.modl | base64 | tr '+/' '-_' | tr -d '=\n')"
The links in these docs are generated by scripts/build-docs.mjs in the
repository, which does the same thing in Node.
Short links with HashBin
#h1= links grow with the diagram. Save to HashBin uploads the diagram to
hashbin.org and replaces the URL with a short content ID:
https://curtcox.github.io/modl/#<256t CID>
- The CID is a 256t string derived from the content, and anyone with the link can
open it. The editor fetches the content from
256t.us. - Saving requires connecting a HashBin account. It is paid from your account balance, usually $0.01, and the diagram is kept for your account's default retention period.
- Saved content can't change. Editing a saved diagram switches the URL back to the long
#h1=form until you save again. Saving an unchanged diagram again extends how long it is kept. - A saved diagram is stored as JSON:
{"format": "hashbin-diagram", "version": 1, "src": …, "pos": …}. #<CID>also opens any plain-text file on HashBin, treating the whole file as modl source with automatic layout.
Tiny diagrams fit inside the CID
A 256t CID for content of 64 bytes or less holds the content itself: 8 characters encoding the length in bytes,
followed by the content in base64url. The editor reads such a CID directly without contacting HashBin, so a very
short diagram can be shared as a compact link built by hand. For example, #AAAAAAAGYSAtPiBi is the 6-byte
source a -> b. The editor itself always saves the JSON form, which is too large to fit inside a CID.