|
| 1 | +--- |
| 2 | +title: Iterating on Diagrams |
| 3 | +description: How drawmode helps LLMs refine and edit diagrams across multiple turns |
| 4 | +--- |
| 5 | + |
| 6 | +drawmode includes several features designed to help LLMs iterate on diagrams — editing existing work, tracking changes, and getting feedback on what was modified. |
| 7 | + |
| 8 | +## Two Editing Workflows |
| 9 | + |
| 10 | +### Workflow 1: Sidecar Source (Recommended) |
| 11 | + |
| 12 | +When you render to `.excalidraw`, drawmode saves the TypeScript source alongside it as a `.drawmode.ts` sidecar file: |
| 13 | + |
| 14 | +``` |
| 15 | +diagram.excalidraw ← Excalidraw JSON (for viewing) |
| 16 | +diagram.drawmode.ts ← TypeScript source (for editing) |
| 17 | +``` |
| 18 | + |
| 19 | +To iterate, the LLM reads the `.drawmode.ts` file, modifies the code, and re-executes. This is the preferred workflow because the TypeScript source is always easier to modify than binary Excalidraw JSON. |
| 20 | + |
| 21 | +### Workflow 2: fromFile (No Source Available) |
| 22 | + |
| 23 | +When no sidecar exists, `fromFile()` reconstructs a Diagram from raw Excalidraw JSON: |
| 24 | + |
| 25 | +```typescript |
| 26 | +const d = await Diagram.fromFile("diagram.excalidraw"); |
| 27 | +const ids = d.findByLabel("Old Service"); |
| 28 | +if (ids.length > 0) d.updateNode(ids[0], { label: "New Service", color: "ai" }); |
| 29 | +d.removeNode(d.findByLabel("Deprecated")[0]); |
| 30 | +return d.render({ path: "diagram.excalidraw" }); |
| 31 | +``` |
| 32 | + |
| 33 | +This parses all nodes, edges, groups, and frames from the file, letting the LLM query and modify them programmatically. |
| 34 | + |
| 35 | +## Querying the Graph |
| 36 | + |
| 37 | +Find and inspect existing elements before modifying them: |
| 38 | + |
| 39 | +```typescript |
| 40 | +// Search by label (substring match) |
| 41 | +const ids = d.findByLabel("API"); |
| 42 | + |
| 43 | +// Exact match |
| 44 | +const ids = d.findByLabel("PostgreSQL", { exact: true }); |
| 45 | + |
| 46 | +// Get all node IDs |
| 47 | +const allNodes = d.getNodes(); |
| 48 | + |
| 49 | +// Get all edges |
| 50 | +const allEdges = d.getEdges(); |
| 51 | +// → [{ from: "n1", to: "n2", label: "queries" }, ...] |
| 52 | + |
| 53 | +// Inspect a specific node |
| 54 | +const info = d.getNode(id); |
| 55 | +// → { label, type, width, height, backgroundColor, strokeColor, row, col } |
| 56 | +``` |
| 57 | + |
| 58 | +## Modifying Elements |
| 59 | + |
| 60 | +### Update Nodes |
| 61 | + |
| 62 | +Change any property on an existing node: |
| 63 | + |
| 64 | +```typescript |
| 65 | +d.updateNode(id, { label: "API Gateway v2", color: "ai" }); |
| 66 | +d.updateNode(id, { width: 200, strokeStyle: "dashed" }); |
| 67 | +d.updateNode(id, { x: 300, y: 100 }); // absolute positioning |
| 68 | +``` |
| 69 | + |
| 70 | +### Update Edges |
| 71 | + |
| 72 | +Modify edge properties. Use `matchLabel` to disambiguate when multiple edges connect the same pair: |
| 73 | + |
| 74 | +```typescript |
| 75 | +d.updateEdge(apiId, dbId, { label: "writes", style: "dashed" }); |
| 76 | +d.updateEdge(apiId, cacheId, { strokeColor: "#e03131" }, "reads"); |
| 77 | +``` |
| 78 | + |
| 79 | +### Remove Elements |
| 80 | + |
| 81 | +Delete nodes (and their connected edges) or specific edges: |
| 82 | + |
| 83 | +```typescript |
| 84 | +d.removeNode(deprecatedId); // also removes all connected edges |
| 85 | +d.removeEdge(apiId, dbId); // remove first matching edge |
| 86 | +d.removeEdge(apiId, dbId, "reads"); // remove by label |
| 87 | +d.removeGroup(groupId); // remove group, keep children |
| 88 | +d.removeFrame(frameId); // remove frame, keep children |
| 89 | +``` |
| 90 | + |
| 91 | +## Change Summary |
| 92 | + |
| 93 | +When overwriting an existing `.excalidraw` file, drawmode automatically computes a diff and reports what changed: |
| 94 | + |
| 95 | +``` |
| 96 | ++ Added: "Cache Layer" (rectangle), "Load Balancer" (rectangle) |
| 97 | +- Removed: "Old Service" (rectangle) |
| 98 | +~ Modified: "API" → "API Gateway v2" |
| 99 | +~ Moved: "Database" |
| 100 | ++ 2 edge(s) added |
| 101 | +- 1 edge(s) removed |
| 102 | + Unchanged: 8 node(s) |
| 103 | +``` |
| 104 | + |
| 105 | +This summary is returned in the MCP tool response, helping the LLM verify its changes and decide if further iteration is needed. |
| 106 | + |
| 107 | +A `.bak` backup is also created automatically before overwriting, so changes are always reversible. |
| 108 | + |
| 109 | +## Diagram Statistics |
| 110 | + |
| 111 | +Every render includes statistics in the response: |
| 112 | + |
| 113 | +``` |
| 114 | +12 nodes, 15 edges, 3 groups |
| 115 | +``` |
| 116 | + |
| 117 | +This gives the LLM a quick sanity check — if the count is unexpectedly low or high, something may have gone wrong. |
| 118 | + |
| 119 | +## Validation Warnings |
| 120 | + |
| 121 | +The Zig WASM validator checks rendered output for structural issues. Warnings are surfaced in the MCP response: |
| 122 | + |
| 123 | +``` |
| 124 | +⚠ Element "n3" has no connections |
| 125 | +⚠ Arrow "e2" has invalid binding |
| 126 | +``` |
| 127 | + |
| 128 | +These guide the LLM to fix problems in the next iteration. |
| 129 | + |
| 130 | +## Mermaid Import |
| 131 | + |
| 132 | +As an alternative starting point, `fromMermaid()` converts Mermaid syntax into a Diagram: |
| 133 | + |
| 134 | +```typescript |
| 135 | +const d = Diagram.fromMermaid(` |
| 136 | + graph TD |
| 137 | + A[API Gateway] --> B[Auth Service] |
| 138 | + A --> C[Order Service] |
| 139 | + B --> D[(Database)] |
| 140 | +`); |
| 141 | +// Now modify using the full SDK |
| 142 | +d.updateNode(d.findByLabel("API Gateway")[0], { color: "backend" }); |
| 143 | +return d.render(); |
| 144 | +``` |
| 145 | + |
| 146 | +Supported Mermaid features: `graph TD/LR/RL/BT`, node shapes (`[]`, `{}`, `(())`, `[()]`), edges (`-->`, `---`, `-..->`, `==>`), edge labels (`|label|`), subgraphs, and chained edges. |
0 commit comments