Files
diagrams-drawio/references/rules-layout.md
oleg-lukasonokandClaude Code 6584df0666 Fix review findings: importer DoS, sync data loss, views/profile rendering, docs
Source importers: lstat before open so FIFOs no longer hang the directory
walk; replace the quadratic Rust macro regex with a linear scan.
Three-way sync: report page add/add and delete-vs-modify as conflicts
instead of silently overwriting or resurrecting; canonical comparison so
key order is not a change; the sync action withholds output and fails on
conflicts unless --force. Story publishing escapes <, >, & and U+2028/9
inside the embedded JSON.
Views: drop parentId of unselected containers, re-layout instead of
manual geometry, validate before serialising; flatten() merges identical
nodes repeated across pages so C4 output works with every analysis
action. Dark theme edge labels get a background; tube-map corridors sit
above the stations; C4 containers keep the swimlane style and orphan
relationships land on the matching page; router keeps container header
bands as obstacles; sequence self-messages loop on one side.
Docs: SKILL.md lists all 23 CLI actions and how each capability family
is invoked, task wrappers for query/test/what-if/doctor, capability
tables and --page scope corrected, maintenance snippet uses the real
synchronous action signature, duplicated rule bullets moved to the rule
references, coverage matrix wording made verifiable.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-06 17:01:50 +03:00

535 lines
29 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# diagrams-drawio — Layout Rules
Connector placement and layout rules for draw.io diagrams generated by this skill.
These rules are **MANDATORY** — apply them when generating any diagram.
---
## General layout rules — MANDATORY
- Use a **10pt grid**; align all shapes to grid increments
- Leave ≥ 40px horizontal and ≥ 40px vertical gaps between shape columns/rows for connector corridors
- Keep connector labels short; place them in clear corridors, never overlapping shapes or other labels
- Before finalising, verify: no shape overlap, no connector-shape overlap, no label overlap, no page clipping
- **Universal 40pt spacing rule** — every shape must have at least 40pt of clear space on **all four sides**:
- **From parent header bottom**: child `y = startSize + 40` (measured from top of swimlane including header)
- **From parent sides**: child `x ≥ 40` from the left/right inner border of the swimlane
- **From parent bottom**: bottom of last child row + 40 = swimlane total height
- **Between siblings** (same nesting level): gap between any two adjacent shapes (horizontal or vertical) = 40pt exactly — all sibling gaps must be identical
Swimlane height formulas:
- Single row: `total_height = startSize + 40 + child_height + 40`
- Two rows: `total_height = startSize + 40 + row1_h + 40 + row2_h + 40`
- Round up total to the nearest multiple of 40 if needed; absorb any remainder into the bottom gap (≥ 40)
Examples:
- `startSize=40`, 1 row h=40: total = 40+40+40+40 = 160 (children at y=80, children w=160 with x-gap=40 between them)
- `startSize=40`, 1 row h=80: total = 40+40+80+40 = 200 (children at y=80)
- `startSize=40`, 2 rows h=40 each: total = 40+40+40+40+40+40 = 240 (row1 at y=80, row2 at y=160)
After generating or editing a diagram, run `page-shape-bbox-validation`, `page-connectors-validation`, and `page-hierarchy-full` (geometry per nesting level) to verify.
### Page size
- Use page dimensions that fully fit the diagram, **including margins, legends, and connector routing corridors** — never let content clip the page edge
- `page-recommendations` reports the smallest standard page size that fits the content with an 80 px margin
### Parent (container) sizing — MANDATORY
**A parent shape must always be large enough to fully enclose all its children, including inner padding.** When children grow (added, resized, or repositioned), expand the parent using:
```
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)` and `max_child_bottom = max(child.y + child.height)` over all children (relative to the parent). Left/top inner padding must also be ≥ 40 pt and divisible by 40 pt.
- **Cascade rule:** parent expansion may force *its* parent to expand. Walk up the containment tree and resize each ancestor until the outermost container fits all descendants
- **Re-check after expansion:** when a parent grows, re-verify sibling spacing at every affected level — all gaps must remain divisible by 40 pt
### Grouping and complexity
- Keep diagram complexity medium: group services into logical zones/layers (swimlanes or containers per major domain) instead of scattering many unrelated services
- Never route dense connector bundles directly through container titles/headers (see label-crossing prohibition below)
### Connector labels and legends
- In dense diagrams keep connector labels **off the connector path**; if labels are needed, place them in a clearly empty corridor, a side note, or a dedicated legend
- Prefer a separate legend for flow explanations whenever connector labels would clutter the diagram
- Keep legends **outside the main routing area** so no connector crosses legend text
- Connector labels must never overlap shapes, cards, icons, or other text
### Final visual inspection
Before completion, re-open or visually inspect the diagram and check specifically for:
- shape overlap (including external actors, cards, icons, notes, legends, containers)
- connector overlap with cards/icons
- connector label overlap
- text overflowing card boundaries
- page clipping
- inconsistent spacing between lanes/columns and between rows
- missing canonical vendor shapes
---
## Diagram Layers and Connector Rules — MANDATORY
### Layer model
| Layer | Contents | Connector may overlap? |
|---|---|---|
| **0 — Background / containers** | Swimlanes, zone boundaries, grouping rectangles | **YES** — connectors may pass through containers |
| **1 — Primary shapes** | Core nodes: main system boxes, components, use cases | **NEVER** |
| **2 — Actor shapes** | Human actors (stick figures), external IT systems | **NEVER** |
| **3 — Connectors** | Edges, waypoints, routing corridors | **AVOID** — crossing other connectors is a last resort |
| **4 — Labels / annotations** | Edge labels, callouts, legend text, title blocks | **NEVER** |
> **Core rule: a connector may only overlap Layer 0. It must NEVER overlap Layers 1, 2, or 4.**
### Label-crossing prohibition — MANDATORY
**A connector must NEVER pass through the textual label of any shape — regardless of which layer the shape belongs to.**
- Container shapes (Layer 0 / swimlanes) have a header bar at the top that contains the label. Connectors must not pass through this header area
- For swimlanes with `startSize=40`, the header occupies `y` to `y+40`. Any connector that enters or exits vertically through the header is forbidden; route it through the side or bottom instead
- Leaf shapes (Layer 1/2) also carry labels — their entire bounding box is off-limits (existing rule), so this adds no extra constraint for those
- To check: identify every shape whose label bounding box (header bar for containers, full box for leaves) intersects a connector segment. Reroute any that does
### Corner-cutting prohibition — MANDATORY
**A connector must NEVER route across the corner of any shape — regardless of which layer the shape belongs to.**
A corner cut occurs when an orthogonal connector exits from one face of a shape and its first waypoint is positioned diagonally (same quadrant as the corner) relative to that exit point, causing the L-shaped segment to visually "clip" the corner region. This is forbidden even when the corner is technically outside the shape bounding box.
Rule: after choosing the exit face and direction of travel, the first waypoint must be placed such that the initial segment travels **parallel to or away from** the corner edge, not at an angle toward it.
| Exit face | First segment must go | Forbidden direction |
|---|---|---|
| Bottom | Down (↓) or laterally clear | Up (↑) toward corner |
| Top | Up (↑) or laterally clear | Down (↓) toward corner |
| Left | Left (←) or clear | Right (→) toward corner |
| Right | Right (→) or clear | Left (←) toward corner |
### Prefer-downward routing — MANDATORY
**When a connector can reach its target by routing either upward or downward, always prefer the downward route.**
Routing upward is only permitted when:
- The target is physically above the source (the upward direction is the natural one), OR
- Routing downward would create a connector-shape overlap that cannot otherwise be avoided
Violation example: a connector exits the bottom of a source shape, immediately routes **up** through the source's parent container header, travels horizontally, then comes back **down** — this is forbidden. The first segment after exiting the bottom face must go **down**, not up.
Decision rule:
1. Identify whether target `y_center` is above or below source `y_center`
2. If below (or same level): exit source bottom or side → route **downward** → enter target top or side
3. If above: exit source top or side → route **upward** → enter target bottom or side
4. If the natural downward path is blocked, use a side-exit (left or right) corridor to bypass, then continue downward — do NOT reverse direction to go upward
### Header-edge prohibition — MANDATORY
**A connector must NEVER run along (or within 1px of) the bottom edge of a swimlane/container header bar.**
The header bar of a swimlane occupies the band `[shape.y .. shape.y + startSize]`. Its **bottom edge** is the line `y = shape.y + startSize`. Connectors that travel horizontally across this line are visually indistinguishable from the header border and create a confusing, cluttered diagram.
- For a swimlane with `startSize=40`, the forbidden horizontal band is within 1px of `shape.y + 40`
- Connectors entering or exiting the swimlane via a **vertical segment that straddles** the header-bottom edge are also forbidden
- **Fix:** reroute the horizontal segment to travel either:
- **Inside** the swimlane body (below `shape.y + startSize + TOL`), or
- **Outside / above** the swimlane entirely (above `shape.y - TOL`), or
- Via a side corridor that avoids the header band altogether
**Detection:** `page-connectors-validation` action reports `headerEdgeViolations` — always run it before finishing any diagram that contains swimlanes or container shapes.
| Situation | Fix |
|---|---|
| Horizontal segment at `y ≈ container.y + startSize` | Move segment down into the swimlane body or up above the container |
| Vertical segment straddles header-bottom edge from inside | Re-enter the swimlane via left or right face instead |
| Multiple connectors fanning across the Data Layer header | Use a horizontal corridor at `y = container.y + startSize + 40` (center of the 40pt gap) or route via side bypass |
### Connector-crossing avoidance — MANDATORY
**Connectors must not cross each other.** Crossing another connector is a last resort — acceptable only when no feasible reroute exists. When a crossing is unavoidable, it must be a clean 90° crossing. Connectors crossing other connectors is always preferable to a connector crossing a shape label or shape corner.
| Cause | Fix |
|---|---|
| Multiple left-side sources fan into one target | Route through a shared left-side vertical corridor; stagger y-entry points |
| Sources on both sides of a central shape | Use separate left-corridor and right-corridor |
| Sources in different rows heading to the same column | Give each source its own horizontal lane in the corridor |
| Long connector crosses shorter ones | Route long connectors through Y_TOP (above all shapes) |
**Crossing-free strategy:**
1. Identify source groups and target groups
2. Assign each source group its own corridor lane
3. Route connectors in each group in parallel through their lane
4. Trace every connector pair — if any two intersect at a non-edge point, redesign
If a crossing truly cannot be avoided, prefer a right-angle (90°) crossing over an oblique one.
### Minimal waypoints rule — MANDATORY
**Add waypoints only when the direct path would clip a Layer 1/2/4 shape.**
| Situation | Waypoints? |
|---|---|
| Source and target at same horizontal level, path is clear | **NO** |
| Adjacent columns with clear gap between them | **NO** |
| Source mid_y falls within target's y-range, path is clear | **NO** |
| Path would clip an intermediate shape | **YES** — minimum waypoints to route around it |
| Fan-out from a central shape to a stacked column | **YES** — use Pattern E |
**Before adding a waypoint, ask:** *"Would the direct auto-routed path clip any Layer 1/2/4 shape?"* If no — no waypoints. If yes — first try repositioning shapes (layout-first principle).
**Keep all connectors the same edge style** (`edgeStyle=orthogonalEdgeStyle`). Never change edge style to work around a layout problem — fix the layout instead.
### Layout-first principle
**Redesign the layout to eliminate waypoints before adding them.**
| Problem | Layout fix |
|---|---|
| Actor above target's y-range | Move target up, or tighten actor spacing so target mid_y aligns |
| Actor below target's y-range | Move target down, or tighten actor spacing |
| Fan-out column spans much more than source | Centre source vertically on the column |
**Shape size is driven by content, not routing** — typically 80–160px tall. If only 1–2 actors fall outside the target's y-range, accept 2 minimal waypoints per connector rather than inflating the shape. Space actors 80–120px apart (centre-to-centre) to keep groups compact.
### When auto-routing is forbidden
Never rely on `edgeStyle=orthogonalEdgeStyle` without explicit waypoints when the path passes through intermediate shapes. Use waypoints only where needed — the goal is the minimum number that achieves zero overlaps and zero crossings.
---
## Connector Routing Patterns
### Corridors
Before placing connectors, identify clear **corridors** — bands free of all shapes.
**Vertical corridor** between two columns:
```
GAP_x = (right_edge_of_left_column + left_edge_of_right_column) / 2
```
**Horizontal corridor** between two rows:
```
GAP_y = (bottom_edge_of_upper_row + top_edge_of_lower_row) / 2
```
Reserve a **top corridor** (`Y_TOP`) above all shapes and a **bottom corridor** (`Y_BELOW`) below all shapes for cross-diagram connectors.
### Absolute coordinates
Connector waypoints are canvas-absolute. For nested containers, sum all ancestor offsets:
```
abs_x = shape.x + parent.x + grandparent.x + ...
abs_y = shape.y + parent.y + grandparent.y + ...
```
### Overlap verification (Python)
```python
TOL = 2
def segment_overlaps_shape(seg, shape):
sx1, sy1, sx2, sy2 = shape
if seg['type'] == 'H':
Y, xA, xB = seg['y'], min(seg['x1'],seg['x2']), max(seg['x1'],seg['x2'])
if sy1+TOL < Y < sy2-TOL and max(xA,sx1+TOL) < min(xB,sx2-TOL):
return True
elif seg['type'] == 'V':
X, yA, yB = seg['x'], min(seg['y1'],seg['y2']), max(seg['y1'],seg['y2'])
if sx1+TOL < X < sx2-TOL and max(yA,sy1+TOL) < min(yB,sy2-TOL):
return True
return False
```
Exclude the terminal shape (source/target) when checking first/last segments.
### Pattern A — Top highway
Long connectors that span the full diagram width:
```
source → V up to Y_TOP → H across → V down to target
```
### Pattern B — Stacked column approach
Multiple targets stacked vertically in a column — never route through the column. Approach each from a corridor to the side:
```
source → V to Y_TOP → H to GAP_x (beside column) → V to target_mid_y → H into target
```
### Pattern C — Narrow gap corridor
When two adjacent shapes leave a small gap (≥ 10px), that gap is a usable vertical corridor:
```
corridor_x = (shape_A_right + shape_B_left) / 2
```
### Pattern D — Bypass around a dense row
When a row of shapes blocks a path to shapes below, go around the right edge:
```
source → H right to RIGHT_BYPASS → V to target_y → H left to target
RIGHT_BYPASS = (rightmost_shape_right + next_column_left) / 2
```
### Pattern E — Fan-out from a central shape to a stacked column
The most common overlap scenario: one central shape connects to N stacked targets on one side.
**Solution — vertical fan-out corridor:**
1. `GAP = (central_right + column_left) / 2`
2. Exit central shape → H to GAP → V to target_mid_y → H into target
```xml
<Array as="points">
<mxPoint x="{GAP}" y="{source_mid_y}"/>
<mxPoint x="{GAP}" y="{target_mid_y}"/>
</Array>
```
**Example** — source mid_y=200, GAP=820, 5 stacked targets:
| Target | mid_y | Waypoints |
|---|---|---|
| Item A | 80 | (820, 200) → (820, 80) |
| Item B | 200 | direct (no waypoints needed) |
| Item C | 320 | (820, 200) → (820, 320) |
| Item D | 440 | (820, 200) → (820, 440) |
| Item E | 560 | (820, 200) → (820, 560) |
Constraint: `central_right < GAP < column_left`. If gap < 40px, move the column right.
### Waypoint XML format
```xml
<mxCell id="conn-1" value="label" style="edgeStyle=orthogonalEdgeStyle;html=1;" edge="1" source="A" target="B" parent="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="450" y="40"/>
<mxPoint x="820" y="40"/>
<mxPoint x="820" y="320"/>
</Array>
</mxGeometry>
</mxCell>
```
Rules:
- Waypoints are canvas-absolute
- Edges are children of root layer (`parent="1"`), never of a container
- Last waypoint stops at the corridor boundary — draw.io completes the final stub
### Layout checklist
- [ ] ≥ 40px gaps between columns and rows (universal 40pt spacing rule)
- [ ] Y_TOP corridor reserved above all shapes
- [ ] Y_BELOW corridor reserved below all shapes
- [ ] Corridor bands contain no shapes
- [ ] Python overlap verification run; all overlaps fixed
- [ ] Endpoint-touch exclusion applied for first/last segments
- [ ] Every connector pair traced — no path intersections at non-edge points
- [ ] All connector labels in clear corridors, not overlapping shapes or other labels
- [ ] No connector passes through any shape's text label (including swimlane header bars)
- [ ] No connector cuts across a shape corner (first segment after exit travels away from corners)
- [ ] No connector runs along (or within 1px of) the bottom edge of any swimlane/container header bar (`page-connectors-validation` reports `headerEdgeViolations`)
- [ ] All connectors route downward when target is below source; upward only when target is above source
---
## Swimlane / Multi-Zone Diagram Routing
When a diagram uses **swimlane zones** (horizontal bands, each a `swimlane` container), apply the following rules in addition to all rules 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 row1 or 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..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
### Swimlane validation workflow
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`
---
## Sequence Diagram Layout Rules — MANDATORY
Sequence diagrams have a fundamentally different structure to architecture diagrams. The rules below override or supplement the general connector rules **for sequence diagrams only**.
### Participants (lifeline headers)
- **Participant boxes** are rectangular shapes (`rounded=0`) placed in a single horizontal row at the top of the diagram
- **HUMAN_ACTOR participants** use `shape=actor`, `width=40`, `height=40` — same as all actor shapes
- **Service/system participants** use `rounded=0`, `width=120`, `height=80` (both divisible by 40)
- **Participant spacing**: gap between adjacent participant boxes must be ≥ 40pt and divisible by 40. Preferred gap = 80pt, giving a step of `width + 80` between participant x-origins
- **Participant x-origin**: must be on the 10pt grid; preferred to be on the 40pt grid
- **All participant boxes must have the same height** (uniform row)
### Lifelines
- Each participant has exactly one **lifeline** — a vertical dashed edge (`dashed=1; endArrow=none`) centered on the participant
- Lifeline x-coordinate = `participant.x + participant.width / 2` (center of participant)
- Lifeline starts at `y = participant.y + participant.height` (bottom edge of participant box) and ends above the legend/footer area. Stop lifelines at least 40pt above the legend box top edge
- Lifeline color matches the participant stroke color
- **Lifelines are Layer-0 elements** — they are background structural elements, equivalent to swimlane containers. Connectors (message arrows) may cross lifelines freely — this is the defining visual characteristic of a sequence diagram
### Message arrows
- Message arrows are horizontal edges (`endArrow=block; endFill=1`) drawn at exact y-coordinates, traveling between lifeline x-positions
- **Each message occupies its own y-row** — messages must never share the same y-coordinate
- **Vertical step between messages**: minimum 20pt, preferred 20–40pt to ensure labels don't overlap
- **Message arrow y-coordinates** must be on the 10pt grid
- **Label placement**: edge label is above the arrow line; keep it short (≤ 30 chars) to avoid overlap with lifelines it crosses
- **Return messages** (response arrows) travel in the opposite horizontal direction; they are drawn 20pt below the corresponding request arrow
### Step / phase labels
- Phase separator labels ("1. Search & Browse", etc.) are text shapes (`style=text`) positioned in a **left margin column** that must not overlap any lifeline
- Left margin column: `x = 20`, `width ≤ participant_leftmost_x - 20 - 4` (at least 4pt clear gap from the leftmost lifeline)
- Phase labels are placed at the y-coordinate of the first message in that phase, minus 20pt
### Lifeline × message crossing — STRUCTURAL EXEMPTION
**Lifeline × message arrow crossings are structural and expected in sequence diagrams.** They must NOT be treated as violations. The `page-connectors-validation` tool will report these as `connectorCrossings` — accept them without fix.
Specifically:
- A vertical lifeline edge crossing a horizontal message arrow edge = **structural, expected, acceptable**
- Only report as a true crossing violation: two message arrows crossing each other (both horizontal) or two lifelines crossing each other (impossible by construction)
This exemption applies only to lifeline edges (identified by: `dashed=1; endArrow=none; vertical segment spanning many messages`). All other crossing rules remain fully in force.
### Legend container — STRUCTURAL EXEMPTION
Legend sample arrows (short colored stubs showing connector styles) placed inside a plain container shape will be reported as `connectorShapeOverlaps` with the container. This is structural and expected — the legend connectors are intentionally drawn inside the container boundary.
Accept `connectorShapeOverlaps` where:
- The overlapping edge is a legend sample stub (short, non-connected edge with `parent=legend-box`)
- The overlapping shape is the legend container itself (`id=legend-box`)
All other `connectorShapeOverlaps` with non-container leaf shapes must still be fixed.
### Sequence diagram validation targets
| Metric | Target | Notes |
|---|---|---|
| `connectorCrossings` (lifeline × message) | **Accepted as structural** | Expected behavior of sequence diagrams |
| `connectorCrossings` (message × message) | **0** | Two horizontal messages must never cross |
| `connectorShapeOverlaps` (legend stubs × legend box) | **Accepted as structural** | Legend connectors inside their container |
| `connectorShapeOverlaps` (message × participant box) | **0** | Message arrows must not pass through participant boxes |
| `connectorShapeOverlaps` (lifeline × step-label shape) | **0** | Step labels must be in a clear left margin column |
| `cornerPortViolations` | **0** | |
| `headerEdgeViolations` | **0** | |
| `singlePortViolations` | **0** | |
| `overlappingPairs` (shape bbox) | **0** | No shape bounding boxes overlap |
### Sequence diagram layout checklist
- [ ] All participants in one horizontal row, uniform height, gaps divisible by 40
- [ ] HUMAN_ACTOR participants: `shape=actor`, width=40, height=40
- [ ] Service participants: `rounded=0`, width=120, height=80
- [ ] Lifelines centered on participants, stop 40pt above legend
- [ ] Lifelines use `dashed=1; endArrow=none`; color matches participant stroke
- [ ] Each message at a unique y-coordinate, on 10pt grid, min 20pt apart
- [ ] Step/phase labels in left margin column (x=20, clear of all lifelines)
- [ ] No message × message crossings (horizontal × horizontal)
- [ ] No message overlapping a participant box
- [ ] Legend items are children of a plain container (not swimlane)
- [ ] `page-shape-bbox-validation` reports `overlappingPairs=0`
- [ ] `page-connectors-validation` reports `cornerPortViolations=0`, `headerEdgeViolations=0`, `singlePortViolations=0`