# Authoring .drawio XML

Read this **before hand-writing any `.drawio` XML** (workflow step 3). Skip it when `drawio_from_mermaid` creates the source for you.


### File skeleton

```xml
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="drawio" version="26.0.0">
  <diagram name="Page-1">
    <mxGraphModel grid="1" gridSize="10" page="1" pageScale="1" pageWidth="1200" pageHeight="800">
      <root>
        <mxCell id="0" />
        <mxCell id="1" parent="0" />
        <!-- user shapes start at id="2" -->
      </root>
    </mxGraphModel>
  </diagram>
</mxfile>
```

**Rules:**
- `id="0"` and `id="1"` are required root cells — never omit them
- User shapes start at `id="2"` and increment sequentially
- All shapes have `parent="1"` (unless inside a container — then use container's id)
- All text uses `html=1` in style for proper rendering
- **Never use `--` inside XML comments** — it's illegal per XML spec and causes parse errors
- Escape special characters in attribute values: `&amp;`, `&lt;`, `&gt;`, `&quot;`
- **Multi-line text in labels:** use `&#xa;` for line breaks inside `value` attributes (not literal `\n`). Example: `value="Line 1&#xa;Line 2"`
- Keep `page="1"` and `pageScale="1"` for a normal finite page, and resize `pageWidth`/`pageHeight` to the finished content. Use `page="0"` only when the user explicitly wants an infinite/non-page canvas; deterministic page-boundary, outer-margin, and empty-space checks are then intentionally skipped.
- If `pageWidth` or `pageHeight` is omitted, DrawMe still validates cells and containment but records that page-boundary, outer-margin, and empty-space checks were skipped. Prefer explicit dimensions for deliverables.

### Canvas sizing and content bounds

The page follows from the layout, never the reverse: author elements with generous spacing first, then run `drawio_fit_canvas` to resize `pageWidth`/`pageHeight` to the visible content plus a margin (default 40px) and shift content to the margin origin. Run it again after every move, resize, container change, layout pass, legend addition, or waypoint edit, and treat each run as a source edit to validate.

When elements overlap or cannot fit, do not shrink or cram them to preserve the current page: move them apart with proper spacing and refit the canvas so the page grows around them. The reverse problem — a page much larger than content — creates unreadable full-diagram previews and an excessive-empty-space warning; the same fit run tightens it.

Background for manual sizing (what the fit computes): visible top-level vertex/container rectangles as `(x, y, x + width, y + height)` with ancestor container offsets added for nested children, expanded by separately sized edge labels and explicit waypoints; content shifted so minimum `x`/`y` sit at the margin (DrawMe warns below the **20px minimum**); `pageWidth`/`pageHeight` set to the bounds plus the margin on each side. Automatic edge routes still need preview inspection, and invisible helpers or intentional relative border ports never count as page-extending content.

### Typography, padding, and labels

- Use at least **12px for body/node labels** and **10px for connector labels**. Headings usually need 16px or more. Compact border ports and notation-mandated annotations may use 8–9px when their short labels remain legible; record the exception during review.
- Add `whiteSpace=wrap;html=1;` to labeled nodes. Use about 10–16px visual padding (`spacing=10;` or directional spacing) between text and shape borders.
- Size a node for the rendered lines, not only character count. A conservative height is `line count × (fontSize × 1.25) + top/bottom padding`; allow additional room for container title bands and icon shapes.
- Estimate width from the longest line (`characters × fontSize × 0.6 + left/right padding`), then verify by preview. Insert deliberate `&#xa;` breaks for long labels rather than forcing a very wide node.
- Prefer two or three concise lines. If a label remains long, enlarge the node, shorten the wording without changing meaning, or split details into a note/legend. Never reduce important body text merely to fit existing geometry.
- A 2000px full-diagram preview scales the entire page. If labels become unreadable at that width, simplify the layout, tighten the canvas, split the diagram into pages, or use a focused review export; never accept an unreadable full view as passing typography.

### Fix recipes: visual defect → source edit

When the preview critique narrows a finding down to specific cells, apply the matching recipe — then validate, re-export, and confirm the defect is gone in the new image:

| Visual defect (seen in the preview) | Fix |
|-------------------------------------|-----|
| Two shapes (or their labels) touch or overlap | Move the shapes apart per the spacing table, then `drawio_fit_canvas` — never shrink content |
| Label text touches or crosses its own shape border | Enlarge the node per the sizing formulas above, add `spacing=10;`, or insert `&#xa;` breaks |
| Label text clipped / cut off mid-word | Same as above — the node is too small for the rendered lines |
| Edge label struck through by its own line, or hugging it | Lift the label off the line with an offset in the edge's label geometry (example below) and add `labelBackgroundColor=#ffffff;` |
| Edge label overlaps another edge, label, or shape | Slide it along the edge (geometry `x` between `-1` and `1`) toward a clear stretch, plus the offset/background above |
| Any text with a line running underneath it | Reroute the edge around the text with waypoints, or move the label — text over lines is never acceptable |
| Arrowhead buried in a bend or hidden by a shape | Keep the final segment before the target ≥20px: move the last waypoint or increase spacing |
| Edge passes through a shape it does not connect | Add waypoints around it, or move the shape out of the routing corridor |
| Two edges stacked on the same path | Spread their entry/exit points (see the distribution table below) |
| Icon shape's label overlaps the icon artwork | Move the label outside: `verticalLabelPosition=bottom;verticalAlign=top;labelBackgroundColor=#ffffff;` |
| Content touching a page edge, uneven margins, or big empty areas | Re-space the elements if needed, then run `drawio_fit_canvas` |

**Positioning an edge label** — the edge's own label is placed by its geometry: `x` runs along the edge from `-1` (source) through `0` (center) to `1` (target), and an `<mxPoint as="offset">` shifts it in page pixels (negative `y` = up):

```xml
<mxCell id="e2" value="HTTP/REST" style="edgeStyle=orthogonalEdgeStyle;rounded=1;html=1;labelBackgroundColor=#ffffff;" edge="1" parent="1" source="2" target="3">
  <mxGeometry x="-0.25" relative="1" as="geometry">
    <mxPoint as="offset" x="0" y="-12" />
  </mxGeometry>
</mxCell>
```

### Shape types (vertex)

| Style keyword | Use for |
|--------------|---------|
| `rounded=0` | plain rectangle (default) |
| `rounded=1` | rounded rectangle — services, modules |
| `ellipse;` | circles/ovals — start/end, databases |
| `rhombus;` | diamond — decision points |
| `shape=mxgraph.aws4.resourceIcon;` | AWS icons |
| `shape=cylinder3;` | cylinder — databases |
| `swimlane;` | group/container with title bar |

For **vendor/branded icons** (AWS/Azure/GCP/Cisco/Kubernetes) and any non-trivial shape, don't guess the `shape=mxgraph.*` name — a wrong name renders as a blank box. Call `drawio_shapesearch` with specific vendor and component keywords, then copy the returned official style and recommended dimensions. If the bundled index has no suitable logo, use a clearly labeled standard shape or ask the user for an image asset.

### Required properties

```xml
<!-- Rectangle / rounded box -->
<mxCell id="2" value="Label" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
  <mxGeometry x="100" y="100" width="160" height="60" as="geometry" />
</mxCell>

<!-- Cylinder (database) -->
<mxCell id="3" value="DB" style="shape=cylinder3;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;" vertex="1" parent="1">
  <mxGeometry x="350" y="100" width="120" height="80" as="geometry" />
</mxCell>

<!-- Diamond (decision) -->
<mxCell id="4" value="Check?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
  <mxGeometry x="100" y="220" width="160" height="80" as="geometry" />
</mxCell>
```

### Containers and groups

For architecture diagrams with nested elements, use draw.io's parent-child containment — do **not** just place shapes on top of larger shapes.

| Type | Style | When to use |
|------|-------|-------------|
| **Group** (invisible) | `group;pointerEvents=0;` | No visual border needed, container has no connections |
| **Swimlane** (titled) | `swimlane;startSize=30;` | Container needs a visible title bar, or container itself has connections |
| **Custom container** | Add `container=1;pointerEvents=0;` to any shape | Any shape acting as a container without its own connections |

**Key rules:**
- Add `pointerEvents=0;` to container styles that should not capture connections between children
- Children set `parent="containerId"` and use coordinates **relative to the container**

```xml
<!-- Swimlane container -->
<mxCell id="svc1" value="User Service" style="swimlane;startSize=30;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
  <mxGeometry x="100" y="100" width="300" height="200" as="geometry"/>
</mxCell>
<!-- Child inside container — coordinates relative to parent -->
<mxCell id="api1" value="REST API" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="svc1">
  <mxGeometry x="20" y="40" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="db1" value="Database" style="shape=cylinder3;whiteSpace=wrap;html=1;" vertex="1" parent="svc1">
  <mxGeometry x="160" y="40" width="120" height="60" as="geometry"/>
</mxCell>
```

### Connector (edge)

**CRITICAL:** Every edge `mxCell` must contain a `<mxGeometry relative="1" as="geometry" />` child element. Self-closing edge cells (`<mxCell ... edge="1" ... />`) are **invalid** and will not render. Always use the expanded form.

```xml
<!-- Directed arrow — always include rounded, orthogonalLoop, jettySize for clean routing -->
<mxCell id="10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="2" target="3">
  <mxGeometry relative="1" as="geometry" />
</mxCell>

<!-- Arrow with label + explicit entry/exit points to control direction -->
<mxCell id="11" value="HTTP/REST" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="2" target="4">
  <mxGeometry relative="1" as="geometry" />
</mxCell>

<!-- Arrow with waypoints — use when edge must route around other shapes -->
<mxCell id="12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="3" target="5">
  <mxGeometry relative="1" as="geometry">
    <Array as="points">
      <mxPoint x="500" y="50" />
    </Array>
  </mxGeometry>
</mxCell>
```

**Edge style rules:**
- **Animated connectors:** add `flowAnimation=1;` to any edge style to show a moving dot animation along the arrow. Works in SVG export and draw.io desktop — ideal for data-flow and pipeline diagrams. Example: `style="edgeStyle=orthogonalEdgeStyle;flowAnimation=1;rounded=1;..."`
- **Always** include `rounded=1;orthogonalLoop=1;jettySize=auto` — these enable smart routing that avoids overlaps
- Pin `exitX/exitY/entryX/entryY` on every edge when a node has 2+ connections. Pick the side facing each peer and spread that side's edges over evenly spaced slots so they do not stack or cross at the boundary.
- Add `<Array as="points">` waypoints when an edge must detour around an intermediate shape
- **Leave room for arrowheads:** the final straight segment between the last bend and the target shape must be ≥20px long. If too short, the arrowhead overlaps the bend and looks broken. Fix by increasing node spacing or adding explicit waypoints
- **Obstacle-avoiding routing (draw.io ≥ 30):** the desktop editor can recompute connectors around shapes without moving nodes. This is an interactive editor feature, not a `drawio_layout` preset. If a dense diagram still has crossings after preview, offer to open the `.drawio` in draw.io desktop for manual routing.

### Distributing connections on a shape

When multiple edges connect to the same shape, assign different entry/exit points to prevent stacking:

| Position | exitX/entryX | exitY/entryY | Use when |
|----------|-------------|-------------|----------|
| Top center | 0.5 | 0 | connecting to node above |
| Top-left | 0.25 | 0 | 2nd connection from top |
| Top-right | 0.75 | 0 | 3rd connection from top |
| Right center | 1 | 0.5 | connecting to node on right |
| Bottom center | 0.5 | 1 | connecting to node below |
| Left center | 0 | 0.5 | connecting to node on left |

**Rule:** if a shape has N connections on one side, space them evenly (e.g., 3 connections on bottom → exitX = 0.25, 0.5, 0.75)

### Color palette (fillColor / strokeColor)

*Used only when no user style preset is active (see `references/style-presets.md` → "Applying a preset").*

| Color name | fillColor | strokeColor | Use for |
|-----------|-----------|-------------|---------|
| Blue | `#dae8fc` | `#6c8ebf` | services, clients |
| Green | `#d5e8d4` | `#82b366` | success, databases |
| Yellow | `#fff2cc` | `#d6b656` | queues, decisions |
| Orange | `#ffe6cc` | `#d79b00` | gateways, APIs |
| Red/Pink | `#f8cecc` | `#b85450` | errors, alerts |
| Grey | `#f5f5f5` | `#666666` | external/neutral |
| Purple | `#e1d5e7` | `#9673a6` | security, auth |

### Legend (auto-generate from the palette)

When a diagram uses 3+ semantic colors, add a legend so the color coding is self-explanatory. Generate it mechanically from the roles actually present — never invent legend entries that aren't in the diagram:

```xml
<!-- Legend container: place in a corner clear of the diagram (e.g. below-left) -->
<mxCell id="legend" value="Legend" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#666666;verticalAlign=top;fontStyle=1;" vertex="1" parent="1">
  <mxGeometry x="40" y="720" width="180" height="110" as="geometry"/>
</mxCell>
<!-- One swatch + label pair per used role, 24px row pitch, children of the legend -->
<mxCell id="leg1" value="" style="rounded=0;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="legend">
  <mxGeometry x="10" y="30" width="30" height="16" as="geometry"/>
</mxCell>
<mxCell id="leg1t" value="Service" style="text;html=1;align=left;verticalAlign=middle;" vertex="1" parent="legend">
  <mxGeometry x="50" y="28" width="120" height="20" as="geometry"/>
</mxCell>
```

Rules: swatch colors come from the active palette (preset or the table above) with the **role name** as the label (Service, Database, Queue, …); height = `30 + 24 × rows`; the legend is a container (`parent="legend"`, relative coordinates); skip it entirely for single-color diagrams.

### Layout tips

**Spacing — scale with complexity:**

| Diagram complexity | Nodes | Horizontal gap | Vertical gap |
|-------------------|-------|----------------|--------------|
| Simple | ≤5 | 200px | 150px |
| Medium | 6–10 | 280px | 200px |
| Complex | >10 | 350px | 250px |

**Routing corridors:** between shape rows/columns, leave an extra ~80px empty corridor where edges can route without crossing shapes. Never place a shape in a gap that edges need to traverse.

**Grid alignment:** snap all `x`, `y`, `width`, `height` values to **multiples of 10** — this ensures shapes align cleanly on draw.io's default grid and makes manual editing easier.

**General rules:**
- Run `drawio_fit_canvas` after every layout change so `pageWidth`/`pageHeight` track the content; it keeps the recommended 40px outer margin (DrawMe warns below the 20px minimum)
- Plan a grid before assigning x/y coordinates — sketch node positions on paper/mentally first
- Group related nodes in the same horizontal or vertical band
- Use `swimlane` cells for logical grouping with visible borders
- Place heavily-connected "hub" nodes centrally so edges radiate outward instead of crossing
- To force straight vertical connections, pin entry/exit points explicitly on edges:
  `exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0`
- Always center-align a child node under its parent (same center x) to avoid diagonal routing
- **Event bus pattern**: place Kafka/bus nodes in the **center of the service row**, not below — services on either side can reach it with short horizontal arrows (`exitX=1` left side, `exitX=0` right side), eliminating all line crossings
- Horizontal connections (`exitX=1` or `exitX=0`) never cross vertical nodes in the same row; use them for peer-to-peer and publish connections

**Avoiding edge-shape overlap:**
- Before finalizing coordinates, trace each edge path mentally — if it must cross an unrelated shape, either move the shape or add waypoints
- For tree/hierarchical layouts: assign nodes to layers (rows), connect only between adjacent layers to minimize crossings
- For star/hub layouts: place the hub center, satellites around it — edges stay short and radial
- When an edge must span multiple rows/columns, route it along the outer corridor, not through the middle of the diagram

