modl Manual Override Diagram Language Open the editor

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] }
}

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.

Without positions
start square teal Start
build square blue Build
test  square blue Test
ship  square purple Ship
start -> build -> test -> ship
Open in editor
Same source, with positions
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>

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.