Files
diagrams-drawio/SKILL.md
T
oleg-lukasonokandClaude Fable 5 99f1fd07e3 Add drawio-main skill (renamed from drawio-main-v1)
Draw.io diagram generation and analysis skill: mandatory layout and
style rules, XML reference, negative-space companion diagrams, and
the drawio-tools TypeScript CLI with 12 analysis/validation actions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:52:39 +03:00

33 KiB
Raw Blame History

name, description
name description
drawio-main Always use when user asks to create, generate, draw, or design a diagram, flowchart, architecture diagram, ER diagram, sequence diagram, class diagram, network diagram, mockup, wireframe, or UI sketch, or mentions draw.io, drawio, drawoi, .drawio files, or diagram export to PNG/SVG/PDF.

Draw.io Diagram Skill

This skill covers two capabilities:

  1. Diagram generation — create .drawio files (and optionally export to PNG/SVG/PDF) from a description or requirements
  2. Diagram analysis — run the drawio-tools CLI to analyse an existing .drawio file: inventory shapes and connectors, validate layout quality, detect overlaps/orphans, and recommend page sizes

Skill files (read these when using this skill):

  • SKILL-CAPABILITIES.md — full list of capabilities and all CLI analysis actions an agent can execute
  • SKILL-RULES-LAYOUT.md — mandatory layer model, connector-crossing avoidance, waypoint patterns, overlap verification, layout checklist
  • SKILL-RULES-STYLE.md — mandatory visual appearance and sizing rules (colors, dimensions, connector dash styles, shape sizing)
  • SKILL-XML-REFERENCES.md — complete draw.io XML reference (styles, routing, containers, layers, dark mode, well-formedness)
  • SKILL-NEGATIVE-SPACE-DIAGRAM.md — rules for generating negative space companion diagrams from page-negative-space-summary output

Generate draw.io diagrams as native .drawio files. Optionally export to PNG, SVG, or PDF with the diagram XML embedded (so the exported file remains editable in draw.io).

How to create a diagram

  1. Generate draw.io XML in mxGraphModel format for the requested diagram
  2. Write the XML to a .drawio file in the current working directory using the Write tool
  3. If the user requested an export format (png, svg, pdf), locate the draw.io CLI (see below), export with --embed-diagram, then delete the source .drawio file. If the CLI is not found, keep the .drawio file and tell the user they can install the draw.io desktop app to enable export, or open the .drawio file directly
  4. Open the result — the exported file if exported, or the .drawio file otherwise. If the open command fails, print the file path so the user can open it manually

Choosing the output format

Check the user's request for a format preference. Examples:

  • /drawio create a flowchart → flowchart.drawio
  • /drawio png flowchart for login → login-flow.drawio.png
  • /drawio svg: ER diagram → er-diagram.drawio.svg
  • /drawio pdf architecture overview → architecture-overview.drawio.pdf

If no format is mentioned, just write the .drawio file and open it in draw.io. The user can always ask to export later.

Supported export formats

Format Embed XML Notes
png Yes (-e) Viewable everywhere, editable in draw.io
svg Yes (-e) Scalable, editable in draw.io
pdf Yes (-e) Printable, editable in draw.io
jpg No Lossy, no embedded XML support

PNG, SVG, and PDF all support --embed-diagram — the exported file contains the full diagram XML, so opening it in draw.io recovers the editable diagram.

draw.io CLI

The draw.io desktop app includes a command-line interface for exporting.

Locating the CLI

First, detect the environment, then locate the CLI accordingly:

WSL2 (Windows Subsystem for Linux)

WSL2 is detected when /proc/version contains microsoft or WSL:

grep -qi microsoft /proc/version 2>/dev/null && echo "WSL2"

On WSL2, use the Windows draw.io Desktop executable via /mnt/c/...:

DRAWIO_CMD=`/mnt/c/Program Files/draw.io/draw.io.exe`

The backtick quoting is required to handle the space in Program Files in bash.

If draw.io is installed in a non-default location, check common alternatives:

# Default install path
`/mnt/c/Program Files/draw.io/draw.io.exe`

# Per-user install (if the above does not exist)
`/mnt/c/Users/$WIN_USER/AppData/Local/Programs/draw.io/draw.io.exe`

macOS

/Applications/draw.io.app/Contents/MacOS/draw.io

Linux (native)

drawio   # typically on PATH via snap/apt/flatpak

Windows (native, non-WSL2)

"C:\Program Files\draw.io\draw.io.exe"

Use which drawio (or where draw.io on Windows) to check if it's on PATH before falling back to the platform-specific path.

Export command

drawio -x -f <format> -e -b 10 -o <output> <input.drawio>

WSL2 example:

`/mnt/c/Program Files/draw.io/draw.io.exe` -x -f png -e -b 10 -o diagram.drawio.png diagram.drawio

Key flags:

  • -x / --export: export mode
  • -f / --format: output format (png, svg, pdf, jpg)
  • -e / --embed-diagram: embed diagram XML in the output (PNG, SVG, PDF only)
  • -o / --output: output file path
  • -b / --border: border width around diagram (default: 0)
  • -t / --transparent: transparent background (PNG only)
  • -s / --scale: scale the diagram size
  • --width / --height: fit into specified dimensions (preserves aspect ratio)
  • -a / --all-pages: export all pages (PDF only)
  • -p / --page-index: select a specific page (1-based)

Opening the result

Environment Command
macOS open <file>
Linux (native) xdg-open <file>
WSL2 cmd.exe /c start "" "$(wslpath -w <file>)"
Windows start <file>

WSL2 notes:

  • wslpath -w <file> converts a WSL2 path (e.g. /home/user/diagram.drawio) to a Windows path (e.g. C:\Users\...). This is required because cmd.exe cannot resolve /mnt/c/... style paths.
  • The empty string "" after start is required to prevent start from interpreting the filename as a window title.

WSL2 example:

cmd.exe /c start "" "$(wslpath -w diagram.drawio)"

File naming

  • Use a descriptive filename based on the diagram content (e.g., login-flow, database-schema)
  • Use lowercase with hyphens for multi-word names
  • For export, use double extensions: name.drawio.png, name.drawio.svg, name.drawio.pdf — this signals the file contains embedded diagram XML
  • After a successful export, delete the intermediate .drawio file — the exported file contains the full diagram

XML format

A .drawio file is native mxGraphModel XML. Always generate XML directly — Mermaid and CSV formats require server-side conversion and cannot be saved as native files.

Basic structure

Every diagram must have this structure:

<mxGraphModel adaptiveColors="auto">
  <root>
    <mxCell id="0"/>
    <mxCell id="1" parent="0"/>
    <!-- Diagram cells go here with parent="1" -->
  </root>
</mxGraphModel>
  • Cell id="0" is the root layer
  • Cell id="1" is the default parent layer
  • All diagram elements use parent="1" unless using multiple layers

XML reference

For the complete draw.io XML reference including common styles, edge routing, containers, layers, tags, metadata, dark mode colors, and XML well-formedness rules, fetch and follow the instructions at: https://raw.githubusercontent.com/jgraph/drawio-mcp/main/shared/xml-reference.md

Troubleshooting

Problem Cause Solution
draw.io CLI not found Desktop app not installed or not on PATH Keep the .drawio file and tell the user to install the draw.io desktop app, or open the file manually
Export produces empty/corrupt file Invalid XML (e.g. double hyphens in comments, unescaped special characters) Validate XML well-formedness before writing; see the XML well-formedness section below
Diagram opens but looks blank Missing root cells id="0" and id="1" Ensure the basic mxGraphModel structure is complete
Edges not rendering Edge mxCell is self-closing (no child mxGeometry element) Every edge must have <mxGeometry relative="1" as="geometry" /> as a child element
File won't open after export Incorrect file path or missing file association Print the absolute file path so the user can open it manually

CRITICAL: XML well-formedness

  • NEVER include ANY XML comments (<!-- -->) in the output. XML comments are strictly forbidden — they waste tokens, can cause parse errors, and serve no purpose in diagram XML.
  • Escape special characters in attribute values: &amp;, &lt;, &gt;, &quot;
  • Always use unique id values for each mxCell

Additional points

  • Always use a 10pt grid.
  • Align all elements to the grid; avoid freehand / off-grid placement.
  • Use page dimensions that fully fit the diagram, including margins, legends, and connector routing corridors.
  • Keep every main shape's x, y, width, and height aligned to clean grid increments.
  • Prefer element widths and heights divisible by 40; if a canonical vendor icon has a fixed non-divisible size, wrap it inside a grid-aligned card/container.
  • Leave intentional whitespace corridors between columns and rows for connectors.

Same-level shape spacing and parent size rules

Rule: Same-level siblings must be placed as close as possible while maintaining grid-aligned gaps.

Shapes at the same hierarchy level (siblings inside the same parent, or all top-level shapes) must:

  1. Minimise the gap between each other — pack them tightly, leaving only enough space for connectors to pass through.
  2. Gap must be grid-aligned — both the x (horizontal gap) and y (vertical gap) distances between adjacent same-level shapes must be divisible by 40 pt.
Gap type Minimum recommended Must be divisible by
Horizontal gap between siblings (x-axis) 40 pt 40 pt
Vertical gap between siblings (y-axis) 40 pt 40 pt

Example: If shape A ends at x=320 and shape B starts at x=360, the gap is 40 pt (1 grid unit). If shape A ends at x=320 and the gap must be wider for a connector corridor, use x=400 (gap = 80 pt, divisible by 40) — never x=350 (gap = 30 pt, not on a 40 pt boundary).

Rule: A parent (container) shape must always be large enough to fully enclose all its children, including inner padding.

When children grow (new children added, children resized, or children repositioned), the parent container must expand to accommodate them. Apply the following sizing formula:

parent.width  = max_child_right  + right_padding   (right_padding  ≥ 40, divisible by 40)
parent.height = max_child_bottom + bottom_padding  (bottom_padding ≥ 40, divisible by 40)

Where:

  • max_child_right = max(child.x + child.width) over all children (relative to parent)
  • max_child_bottom = max(child.y + child.height) over all children (relative to parent)
  • Left/top inner padding (the space between parent top-left and first child) must also be ≥ 40 pt and divisible by 40 pt.

Cascade rule: Parent expansion may cause its parent to also need expansion. Walk up the containment tree and resize each ancestor in turn until the outermost container fits all descendants.

Positioning rule after expansion: When a parent container grows, re-check sibling spacing at every affected level. All gaps must remain divisible by 40 pt after the resize.

  • Avoid routing connectors through shapes, cards, labels, icons, legends, or containers containing important content.
  • Prefer orthogonal connectors with explicit waypoints when auto-routing causes overlaps.
  • If connector auto-routing crosses shapes, use absolute routed points or fixed waypoints instead of relying on source / target auto-routing.
  • Keep connector labels off the connector path when diagrams are dense.
  • If connector labels are needed, place them in a dedicated legend, side note, or clearly empty corridor.
  • Do not allow connector labels to overlap shapes, cards, icons, or other text.
  • Avoid long text inside narrow shapes; use wider cards or wrap labels in a dedicated text area inside the card.
  • Ensure service names fit within the visual card width and do not extend beyond card boundaries.
  • For service-card diagrams, prefer a consistent card pattern: icon on the left, service label on the right, enough padding around both.
  • Avoid overlapping shapes, including external actors, cards, icons, notes, legends, and containers.
  • When using canonical vendor icons, preserve the original icon styling; adjust surrounding card/container layout instead of modifying the icon style.
  • For rectangles and containers, do not use rounding unless the style explicitly requires it.
  • Avoid borders on shapes unless they are needed for visual separation or canonical styling.
  • Use at most 3 primary color families; create hierarchy with lighter/darker shades instead of adding many unrelated colors.
  • Keep connector colors simple and consistent; use dashed lines only for semantically different flows such as admin, private, async, or backup paths.
  • Keep arrowheads outside shape interiors; connectors should touch shape/card edges, not pass through the body.
  • Validate that all draw.io XML is well-formed and contains required root cells 0 and 1.
  • Ensure all mxCell IDs are unique.
  • Every edge must include an mxGeometry child; use waypoint arrays for routed connectors.
  • For complex diagrams, validate connector paths against shape bounding boxes before finalizing.
  • Prefer a separate legend for flow explanations when connector labels would clutter the diagram.
  • Keep legends outside the main routing area so connectors do not cross legend text.
  • Use consistent spacing between diagram lanes/columns and between rows of cards.
  • Keep diagram complexity medium by grouping services into logical zones/layers instead of scattering many unrelated services.
  • Use swimlanes or containers for major domains, but avoid placing dense connector routes directly through container titles.
  • Before completion, re-open or visually inspect the diagram and check specifically for:
    • shape overlap
    • connector overlap with cards/icons
    • connector label overlap
    • text overflowing card boundaries
    • page clipping
    • inconsistent spacing
    • missing canonical vendor shapes

Connector Routing Best Practices (Zero-Overlap Guarantee)

Complex architecture diagrams with many connectors require deliberate routing. Follow this process to achieve zero connector-shape overlaps.

1. Plan corridors before drawing

Before placing any connectors, identify clear corridors — horizontal or vertical bands on the canvas that are free of all shapes. Route every connector through these corridors.

Vertical corridors (x-axis lanes between columns)

Between each column of shapes, choose a single x-coordinate that lies in the gap:

GAP = (right_edge_of_left_column + left_edge_of_right_column) / 2

Examples from a typical IBM Cloud deployment diagram:

Corridor name x value Description
GAP_AB 370 Between Internet zone and IBM Cloud boundary
GAP_BC 720 Between IBM edge services and VPC
GAP_NS 1510 Between namespace columns inside VPC
GAP_CD 2450 Between VPC right edge and external service group
GAP_DE 2760 Between two external service columns

Horizontal corridors (y-axis lanes between rows)

Between each row of shapes, choose a y-coordinate in the clear gap:

Corridor name y value Description
Y_TOP 110 Above all shapes (highway for cross-diagram connectors)
Y_ROKS 350 Between ingress row and pod row
Y_GW_CORE 640 Between gateway namespace and core namespace
Y_CICD 875 Between ops pods and CI/CD row
Y_BETWEEN 985 Between CI/CD and database row
Y_BELOW 1200 Below all shapes

Rule: When two rows are only 20–40 px apart, the corridor between them is still usable — just ensure the chosen y does not touch any shape's bounding box (see tolerance rule below).

2. Calculate absolute canvas coordinates

draw.io uses relative coordinates for nested containers: a child's x/y is relative to its parent container's top-left corner, not to the canvas. To verify that a connector waypoint (which uses canvas-absolute coordinates) clears a shape, compute:

abs_x = shape.x + parent.x + grandparent.x + ...  (sum all ancestor x offsets)
abs_y = shape.y + parent.y + grandparent.y + ...  (sum all ancestor y offsets)

For swimlane containers, child coordinates are relative to the swimlane's top-left origin (not to the content area below the header). startSize only affects the visual header height — ignore it when computing absolute coordinates.

Build a flat list of all shapes with absolute bounding boxes before routing:

shapes = [
    # (id, abs_x1, abs_y1, abs_x2, abs_y2)
    ("pod-api", 985, 416, 1145, 574),
    ("pod-svc", 810, 416, 967, 574),
    ...
]

3. Verify routes with Python before writing XML

Run an overlap-detection simulation against all shapes before finalising the XML. This prevents invisible bugs that only become apparent when the file is opened.

TOL = 2  # pixels — allow connectors to touch but not overlap interiors

def segment_overlaps_shape(seg, shape):
    sx1, sy1, sx2, sy2 = shape
    if seg['type'] == 'H':  # horizontal segment at y=Y from xA to xB
        Y, xA, xB = seg['y'], min(seg['x1'], seg['x2']), max(seg['x1'], seg['x2'])
        if sy1 + TOL < Y < sy2 - TOL:
            if max(xA, sx1 + TOL) < min(xB, sx2 - TOL):
                return True
    elif seg['type'] == 'V':  # vertical segment at x=X from yA to yB
        X, yA, yB = seg['x'], min(seg['y1'], seg['y2']), max(seg['y1'], seg['y2'])
        if sx1 + TOL < X < sx2 - TOL:
            if max(yA, sy1 + TOL) < min(yB, sy2 - TOL):
                return True
    return False

def route_overlaps(segments, shapes):
    for seg in segments:
        for shape in shapes:
            if segment_overlaps_shape(seg, shape[1:]):  # skip id
                return shape[0], seg
    return None

Convert every connector's waypoint list into a series of H/V segments, then call route_overlaps for each connector. Fix any reported overlap before writing the XML.

Endpoint-touch rule: A connector that terminates at a target shape will appear to overlap that shape in the algorithm because the last segment enters the shape's bounding box. This is expected and correct — exclude the terminal shape (source or target) when checking a connector's first and last segments.

4. Common routing patterns

Pattern A — Top highway for long cross-diagram connectors

Route connectors that must span the full diagram width through Y_TOP (well above all shapes):

source_mid_y → vertical up to Y_TOP → horizontal across to target_col_x → vertical down to target_mid_y

Waypoints example (mxGraphModel format):

<Array as="points">
  <mxPoint x="200" y="110"/>
  <mxPoint x="2600" y="110"/>
</Array>

Pattern B — Stacked services (approach from the left)

When multiple services are stacked vertically in a column (e.g., Watson/AI services), never route a vertical connector through the column. Instead, approach each service individually from a horizontal corridor to its left:

source → vertical to Y_TOP → horizontal to GAP_CD (just left of the column) → horizontal at service_mid_y → connect to service left edge

Each service in the stack gets its own connector that stops at the gap corridor. draw.io completes the final short horizontal stub automatically.

for svc_id, svc_abs_y in stacked_services:
    svc_mid_y = svc_abs_y + svc_height / 2
    waypoints = [
        (source_mid_x, Y_TOP),    # exit source upward
        (GAP_CD, Y_TOP),           # travel across at top highway
        (GAP_CD, svc_mid_y),       # descend to service's row
        # last waypoint stops at corridor; draw.io connects to service edge
    ]

Pattern C — Narrow inter-pod corridors

When adjacent pods in a row leave only a small gap (e.g., pod_A right=967, pod_B left=985 → 18 px gap), that gap is still a usable vertical corridor:

corridor_x = (pod_A_right + pod_B_left) / 2  →  e.g., 976

Use this narrow corridor to route a vertical segment that exits a pod row and connects to shapes above or below:

pod_interior → horizontal to corridor_x → vertical through gap to target_y → horizontal to target

Pattern D — Right bypass for connections below a dense pod grid

When many pods span horizontally across the diagram, route connectors that must reach shapes below by going around the right edge of the pod grid:

source_mid → horizontal right to RIGHT_BYPASS (just right of all pods) → vertical to target_y → horizontal left to target

Set RIGHT_BYPASS to (rightmost_pod_right + left_edge_of_next_column) / 2.

5. Waypoint XML format

Always use the Array as="points" form inside mxGeometry:

<mxCell id="conn-1" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;rounded=1;endArrow=open;endFill=0;" edge="1" source="A" target="B" parent="1">
  <mxGeometry relative="1" as="geometry">
    <Array as="points">
      <mxPoint x="450" y="110"/>
      <mxPoint x="2450" y="110"/>
      <mxPoint x="2450" y="520"/>
    </Array>
  </mxGeometry>
</mxCell>

Rules:

  • All waypoint coordinates are canvas-absolute (not relative to any container).
  • Edges/connectors must be children of the root layer (parent="1"), never children of a container shape.
  • The first waypoint is where draw.io exits the source shape's auto-routing; the last waypoint is where it enters the target.
  • Stop the last waypoint at the corridor boundary — do not pass a waypoint into the interior of the target shape. draw.io will complete the stub automatically.

CRITICAL — Never use port pin constraints on edges

DO NOT add exitX, exitY, exitDx, exitDy, entryX, entryY, entryDx, entryDy attributes to edge mxCell elements.

Indicator Cause Meaning
🔵 Blue circle at connector endpoint source/target IDs set, no port pins Correct — properly connected to shape
🟢 Green circle with white X at connector endpoint Port pin constraints (exitX/Y, entryX/Y) present Wrong — draw.io treats endpoint as floating/unlinked

Port pin attributes force draw.io to attach the connector to a precise computed point on the shape boundary. When the waypoints don't exactly match that computed point, draw.io renders the endpoint as floating (green X). This breaks visual connectivity even though the source/target IDs are set.

Correct pattern — source/target IDs + waypoints only, NO port pins:

<!-- ✅ CORRECT — blue circle, properly connected -->
<mxCell id="edge-001" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;rounded=1;endArrow=open;endFill=0;"
        edge="1" source="shape-A" target="shape-B" parent="1">
  <mxGeometry relative="1" as="geometry">
    <Array as="points">
      <mxPoint x="200" y="420"/>
      <mxPoint x="200" y="200"/>
    </Array>
  </mxGeometry>
</mxCell>

<!-- ❌ WRONG — green X, floating endpoint -->
<mxCell id="edge-001" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;rounded=1;endArrow=open;endFill=0;
        exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;"
        edge="1" source="shape-A" target="shape-B" parent="1">
  ...
</mxCell>

draw.io auto-computes the connection point from source/target shape IDs and the last waypoint direction. Waypoints guide the routing corridor; the actual attachment to the shape is handled automatically.

For «include» / «extend» labels: use real UTF-8 characters «include» / «extend» in the edge value attribute — not XML entities (&#171;include&#187;).

Single-port rule: When multiple edges enter the same target shape, all their last waypoints must approach from the same axis direction (all from left, all from right, all from below, etc.). Mixed directions cause singlePortViolation in page-connectors-validation.

6. Corridor-first layout checklist

Before placing shapes, plan the layout so corridors are naturally available:

  • Leave ≥ 40 px horizontal gaps between columns of shapes
  • Leave ≥ 30 px vertical gaps between rows of shapes
  • Reserve at least one wide horizontal corridor above all shapes (Y_TOP)
  • Reserve at least one wide horizontal corridor below all shapes (Y_BELOW)
  • Do not place shapes in the corridor bands — treat corridors as sacred routing lanes
  • For stacked service columns, leave a vertical corridor to their left at GAP_CD
  • Run Python overlap verification before finalising; fix every reported overlap
  • Apply endpoint-touch exclusion when the last segment enters the target shape

Swimlane / Multi-Zone Diagram Routing

When a diagram uses swimlane zones (horizontal bands, each a swimlane container), apply the following rules in addition to the general patterns above.

Zone layout reference

For a typical layered architecture with startSize=40 swimlanes and 40pt spacing:

zone.y                        ← swimlane top edge
zone.y + 40                   ← header bottom (label occupies y..y+40)
zone.y + 40 + 40 = zone.y+80  ← first child row top (rel y=80 inside container)
zone.y + 80 + child_h         ← first child row bottom
...
zone.y + height               ← swimlane bottom edge

Inter-zone gaps (between adjacent swimlanes) are clean horizontal corridors — use their midpoint as the H-travel y-coordinate for connectors crossing zone boundaries.

Bypass corridors

Always reserve two vertical bypass corridors outside all zones:

Corridor x value Rule
LEFT bypass zone.x - 20 (e.g. x=60 when zones start at x=80) All leftward cross-zone connectors
RIGHT bypass zone.x + zone.width + 20 (e.g. x=1260 when zones end at x=1240) All rightward cross-zone connectors

These bypass corridors run the full canvas height and are free of all shapes. Every connector that must travel between zones should route through one of these corridors.

Multi-row swimlane: horizontal segment placement

When a swimlane has two rows of shapes (row1 and row2), each row has a y-range. Never place a connector H segment at a y-value that falls inside a row's y-range — that will overlap sibling shapes.

Use only these safe H corridors inside a multi-row services swimlane:

Corridor y value When to use
Inter-row gap row1_bottom + (row2_top - row1_bottom)/2 H travel between row1 and row2 shapes
Services-bottom gap row2_bottom + (zone_bottom - row2_bottom)/2 H travel below row2, still inside zone
Inter-zone gaps midpoint of gap between adjacent zones H travel outside zones

Example — services-zone: startSize=40, zone.y=500, row1 abs y=580..620, row2 abs y=660..700, zone bottom=740:

  • Inter-row gap corridor: y=640 (midpoint of y=620..660)
  • Services-bottom corridor: y=720 (midpoint of y=700..740)

Connector routing rules for multi-row swimlanes

Rule 1 — Never route H segments through a row's y-range. If a connector must travel horizontally through the services zone, use y=640 (inter-row) or y=720 (services-bottom), never y=580..620 or y=660..700.

Rule 2 — When exiting a shape in row2 that has sibling shapes to its right in the same row: Do NOT exit from the bottom of the shape and then travel H at y=720 (services-bottom) to the right — this H will cross the V stubs of right-side siblings that exit to the same corridor.

Fix options:

  • a) Exit from the right side of the shape → immediately go to RIGHT bypass x=1260 → V down/up to target (no H inside zone)
  • b) If right-side exit H would cross a sibling shape body: exit right → first waypoint in the column gap to the right (e.g. x=1060 for col5/col6 gap) → V down to services-bottom corridor y=720 → H right to RIGHT bypass → V to target

Rule 3 — Left bypass for connectors going downward to lower zones. When a row2 shape must connect to a shape in a lower zone (messaging, data), route:

shape_bottom → inter-row corridor y=640 → H LEFT to x=LEFT_BYPASS → V down to target zone corridor → H right to target

This ensures the H segment travels in the clear inter-row gap corridor and never crosses sibling shapes.

Rule 4 — connectorShapeOverlaps with zone containers are structural and unavoidable. Any connector crossing a swimlane zone boundary will be flagged as connector_shape_overlap with the zone container. This is expected and acceptable — focus on eliminating connectorCrossings (two connectors intersecting each other) and overlaps with non-container leaf shapes.

Pattern F — Multi-row swimlane fan-out (api-gateway → many services)

When a shape in an upper zone connects to N shapes spread across multiple columns of a multi-row services swimlane:

api-gateway_bottom → H to LEFT_BYPASS at inter-zone gap y → V down LEFT_BYPASS →
  branch per target:
    - col1 (leftmost): H right from LEFT_BYPASS to target_center_x at inter-row gap y
    - col2: same, longer H
    - col3..colN: same pattern, extending H further right
    - for row2 targets: V from inter-row corridor down to row2 top, enter from top

All H branches travel at the same y (inter-row gap) — they are parallel, not crossing.

Pattern G — Event bus → services (right bypass fan-in)

When an event bus (bottom of diagram) connects back up to multiple services:

event-bus_right → H right to RIGHT_BYPASS → V up →
  branch per target:
    - service in row1: H left from RIGHT_BYPASS at row1_center_y to target
    - service in row2: stop at services-bottom corridor y=720 → H left → V up 20pt to target bottom
    - stagger y values slightly (e.g. y=720 for one, y=730 for another) to avoid parallel confusion

Critical: The H segments going LEFT from RIGHT_BYPASS at y=720/730 will cross V stubs of row2 services that exit their bottoms and route DOWN and RIGHT to the right bypass. To avoid these crossings:

  • Route those downward connectors via the LEFT bypass instead (Pattern F inverse)
  • OR ensure the rightward services exit via their right side (not bottom), so no V stub exists at y=720..730

Validation workflow for swimlane diagrams

  1. Run page-connectors-validation after every edit
  2. Fix connectorCrossings first — these are always avoidable
  3. Fix connectorShapeOverlaps with leaf shapes (non-containers) — these are overlaps with actual service boxes
  4. Accept connectorShapeOverlaps with zone containers as structural (unavoidable)
  5. Fix cornerPortViolations, headerEdgeViolations, singlePortViolations — all should reach 0
  6. Target: connectorCrossings=0, cornerPortViolations=0, headerEdgeViolations=0, singlePortViolations=0

Section 8 — Text-width estimation for negative space calculation

When computing negative space manually (e.g. generating a negative-space diagram), shape bounding boxes alone overestimate occupied space. Text labels only occupy a sub-region within the shape bbox. Use the following formula:

charWidth  = fontSize × 0.6           // avg glyph width for proportional fonts
rawWidth   = longestLineCharCount × charWidth
padding    = fontSize × 1.0           // horizontal padding (~0.5em each side)
textWidth  = rawWidth + padding

fontStyle modifiers (draw.io bitmask):
  bit 0 (value 1) = bold   → charWidth × 1.10
  bit 1 (value 2) = italic → charWidth × 1.05

textXMin = clamp(shapeCenterX − textWidth/2, shape.xMin, shape.xMax)
textXMax = clamp(shapeCenterX + textWidth/2, shape.xMin, shape.xMax)
textFlankLeft  = textXMin − shape.xMin   // free space left of text inside bbox
textFlankRight = shape.xMax − textXMax   // free space right of text inside bbox

When to apply:

  • Swimlane headers (startSize=40 band): header text is centered in the full zone width → use text region for occupied X, flanks are free negative space
  • Large container shapes whose children don't fill the full width
  • Any shape where textWidth < shapeWidth — the flanks inside the bbox are free

Example — Marketplace diagram zone headers, fontSize=12 bold (fontStyle=1), center x=660, zone x=80..1240:

Label longestLine chars textWidth textXMin textXMax flank each side
"Client Layer" 12 107 607 713 ~527pt
"API Gateway / Edge Layer" 24 202 559 761 ~479pt
"Core Microservices" 18 155 583 737 ~503pt
"Messaging / Event Bus" 21 178 571 749 ~491pt
"Data Layer" 10 91 615 705 ~535pt

page-negative-space-summary action outputs (per shape):

  • textXMin, textXMax, textWidth — estimated text rendering region
  • textFlankLeft, textFlankRight — free flanks inside bbox
  • rows[].textAwareFreeCorridors — corridors computed using text regions (wider than bbox-based)

When drawing a negative-space diagram: see SKILL-NEGATIVE-SPACE-DIAGRAM.md for all rules, construction steps, XML pattern, file naming, and validation.