{
  "docVersion": 1,
  "id": "template-collaborative-doc-editor",
  "name": "Collaborative doc editor",
  "purpose": "Real-time co-editing: clients send CRDT ops over websockets, a per-document session orders and broadcasts them, an append-only op-log persists history, snapshots compact it.",
  "nodes": {
    "ua": {
      "type": "user",
      "label": "Editor A",
      "x": 20,
      "y": 60,
      "runtime": "types 'hello' at pos 4"
    },
    "ub": {
      "type": "user",
      "label": "Editor B",
      "x": 20,
      "y": 320,
      "runtime": "types concurrently at pos 4"
    },
    "gw": {
      "type": "websocket",
      "label": "WS Gateway",
      "x": 320,
      "y": 190,
      "runtime": "connection registry · sticky by doc",
      "color": "blue",
      "patterns": [
        "autoscale",
        "stateless"
      ],
      "notes": "Terminates sockets, maps connections → documents, routes ops to the right session. Stateless: session affinity is just routing, state lives behind it."
    },
    "sess": {
      "type": "server",
      "label": "Doc session (CRDT)",
      "x": 620,
      "y": 190,
      "runtime": "RGA sequence CRDT · merges ops",
      "color": "purple",
      "notes": "One logical session per open document. Applies ops to the CRDT (concurrent inserts get deterministic positions), assigns causal order, broadcasts to every subscriber."
    },
    "oplog": {
      "type": "storage",
      "label": "Op log",
      "x": 920,
      "y": 60,
      "runtime": "append-only · the truth",
      "patterns": [
        "backup"
      ]
    },
    "snap": {
      "type": "worker",
      "label": "Snapshot compactor",
      "x": 920,
      "y": 330,
      "runtime": "ops → snapshot · truncate",
      "notes": "New joiner = latest snapshot + tail of ops. Without compaction, join time grows forever with document age."
    }
  },
  "edges": {
    "e1": {
      "from": "ua",
      "to": "gw",
      "fromSide": "right",
      "toSide": "left",
      "label": "op: insert('h',pos4)",
      "animated": true
    },
    "e2": {
      "from": "ub",
      "to": "gw",
      "fromSide": "right",
      "toSide": "left",
      "animated": true
    },
    "e3": {
      "from": "gw",
      "to": "sess",
      "fromSide": "right",
      "toSide": "left",
      "label": "route by doc id",
      "animated": true
    },
    "e4": {
      "from": "sess",
      "to": "gw",
      "fromSide": "top",
      "toSide": "top",
      "label": "broadcast merged ops + cursors",
      "style": "dashed",
      "color": "purple"
    },
    "e5": {
      "from": "sess",
      "to": "oplog",
      "fromSide": "right",
      "toSide": "left",
      "label": "append",
      "patterns": [
        "ordered"
      ]
    },
    "e6": {
      "from": "oplog",
      "to": "snap",
      "fromSide": "bottom",
      "toSide": "top",
      "label": "compact periodically",
      "style": "dashed"
    }
  },
  "boxes": {
    "b1": {
      "x": 580,
      "y": -30,
      "w": 640,
      "h": 540,
      "label": "per-document authority — ops in, order out",
      "color": "purple",
      "opacity": 0.04
    }
  },
  "texts": {
    "t1": {
      "x": 20,
      "y": 500,
      "text": "why CRDT beats locks here: both editors insert at\npos 4 simultaneously — the CRDT gives both a stable\nidentity, so A and B converge to the SAME text\nwithout anyone waiting or losing a keystroke.",
      "color": "purple",
      "size": 12
    }
  },
  "meta": {
    "createdAt": "2026-07-13T09:00:00.000Z",
    "modifiedAt": "2026-07-13T09:00:00.000Z"
  }
}