# Spinediagrams

> Generate clean architecture and system-design diagrams as SVG files. Use this skill whenever the user wants to visualise how services, systems, or components connect: migration diagrams, current-state vs target-state architectures, integration maps, data-flow diagrams, platform overviews, or any diagram that shows technology boxes with arrows between them. Triggers on phrases like: "draw a diagram", "make an architecture diagram", "show me how X connects to Y", "create a system diagram", "architecture SVG", "visualise the stack", "diagram this", or whenever the conversation has produced a list of components and integrations that would benefit from a visual. Always use this skill rather than trying to write SVG by hand.

- Skill: `wasulajr/spinediagrams` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add wasulajr/spinediagrams`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wasulajr/spinediagrams/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: wasulajr (https://skillmd.com/u/wasulajr)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/wasulajr/spinediagrams

---


# Architecture Diagram SVG Skill

Produces fixed-width (1600 px) SVG architecture diagrams with:
- A title / subtitle header band at the top
- A **6-column grid** with **up to 3 rows** of technology containers (each container holds colour-coded component nodes)
- **Orthogonal single-spine edge routing**. All connection lines travel through the spine zone(s) between adjacent rows so they never pass through container bodies.
- A status-colour legend at the bottom

## Workflow

1. **Understand the diagram.** Identify the technology containers (lanes), the components inside each, and the connections between them.
2. **Build the config dict** (see format below).
3. **Run the engine** and save the SVG.
4. **Icon gate (MANDATORY).** Every render prints an icon check report. If it
   says `ICON CHECK FAILED`, the diagram is NOT done: resolve every listed node
   (run `/get-icon <technology>`, set an explicit icon key, or rename the node
   to the canonical technology name), re-render, and repeat until the report
   says `Icon check: OK`. Suppress an icon (spine: 5th node field `""`; flow:
   `"icon": ""` on the node) only for genuinely non-technology nodes: people,
   teams, abstract concepts. Never
   present an SVG whose last render reported missing icons.
   (If you rendered via `import` instead of the CLI, print the check yourself:
   `from svg_engine import icon_report; print(icon_report(d))`.)
5. **Present the file** to the user.

## Engine location

```
<skill_dir>/scripts/svg_engine.py
```

Import or call it directly:

```python
# Option A: import
import sys
sys.path.insert(0, "<skill_dir>/scripts")
from svg_engine import Diagram

d = Diagram(config)          # config is a dict (see format below)
svg = d.render()
with open("output.svg", "w") as f:
    f.write(svg)

# Option B: CLI (config must be written to a JSON file first)
# python <skill_dir>/scripts/svg_engine.py config.json output.svg
```

## Config format

```python
config = {
    "title": "Main Title — Optional subtitle",   # " — " (em dash) splits title from subtitle
    "num_cols": 6,    # optional, default 6; increase for more lanes per row
    "aspect": "16:9", # optional, default "16:9". Also accepts "4:3" or a
                      # numeric width/height ratio. Canvas pads to this
                      # ratio by growing the spine, which spaces arrow
                      # labels further apart.

    # ── Lanes (containers) ──────────────────────────────────────────────────
    # Each key becomes the lane identifier used in nodes/connections.
    # For well-known vendors, just use the preset key (no colours needed).
    # For custom lanes, supply bg / border / header_bg.
    "lanes": {
        "sf": {                      # preset key -> colours auto-filled
            "label": "Salesforce",
            "col": 0, "colspan": 2, "row": 0
        },
        "gcp": {
            "label": "GCP Platform",
            "col": 0, "colspan": 2, "row": 1
        },
        "custom": {                  # custom lane -> supply colours
            "label": "My Service",
            "col": 2, "colspan": 1, "row": 0,
            "bg": "#f0fdf4", "border": "#16a34a", "header_bg": "#bbf7d0"
        }
    },

    # ── Nodes ───────────────────────────────────────────────────────────────
    # Each list entry: ["label", "status"] or with optional fields:
    #   ["label", "status"]
    #   ["label", "status", "category"]
    #   ["label", "status", "category", True]          # primary (bold + border ring)
    #   ["label", "status", "category", True, "icon"]  # explicit icon key override
    # Status values: existing | new | transitioning | retiring | operational | readonly
    "nodes": {
        "sf":  [["CRM", "existing"], ["Billing", "transitioning"]],
        "gcp": [["Cloud SQL", "new"], ["API Gateway", "new"]],
        "custom": [["Auth Service", "new"]]
    },

    # ── Connections ─────────────────────────────────────────────────────────
    # Each entry: [src_lane, src_node_label, dst_lane, dst_node_label, edge_label]
    # src/dst_node_label must exactly match a node label in that lane.
    # Edge label is shown inline on the routing line.
    "connections": [
        ["sf",  "Billing",  "gcp", "Cloud SQL",   "Pub/Sub sync"],
        ["gcp", "API Gateway", "sf", "CRM",        "Write-back"]
    ]
}
```

The em dash inside the `title` string is API syntax, not prose. The engine literally splits the title field on ` — ` to separate title from subtitle.

## Grid layout rules

The diagram has up to **3 rows** and up to `num_cols` columns (default 6).

Assign each lane a `col` (0-based), `colspan`, and `row` (`0` = top, `1` = middle, `2` = bottom). Lane widths in a row must not overlap: `col + colspan` for each lane must stay within `num_cols`. Row 2 is optional; omit it for a classic 2-row diagram.

**Suggested 2-row layout:**

| Row | Col 0-1 (span 2) | Col 2 | Col 3 | Col 4 | Col 5 |
|-----|-----------------|-------|-------|-------|-------|
| 0   | Large source (e.g. SF) | Mid-tier A | Mid-tier B | Mid-tier C | (empty) |
| 1   | Large target (e.g. GCP) | Ext A | Ext B | Ext C | Ext D |

**3-row layout (classic 3-tier: frontend / backend / data + externals):**

| Row | Use it for |
|-----|------------|
| 0   | User-facing surfaces (browser, mobile, public APIs) |
| 1   | Backend services / orchestration / business logic |
| 2   | Data stores + external SaaS dependencies |

### Routing rules (important for 3-row diagrams)

Connections route through one of two spines, or, for skip-row connections, through a margin-sidestep channel:

- **Spine 0↔1** (between rows 0 and 1) carries: row 0↔0, row 0↔1, row 1↔1 (different lanes).
- **Spine 1↔2** (between rows 1 and 2) carries: row 1↔2, row 2↔2 (different lanes).
- **Margin sidestep** carries: row 0↔2 (skip-row). The line drops to spine 0↔1, runs out to the canvas margin, drops through the margin past row 1, re-enters spine 1↔2, and drops to the destination. Six segments instead of three, but stays out of all container bodies.
- **Intra-lane channel** carries: connections between two nodes in the SAME lane. The line exits the source node's side, runs vertically in a thin channel just inside the lane border, and enters the destination node's side. Channels alternate sides (right, left, right, ...) per lane and each same-side channel gets its own X; nodes shrink slightly to clear the channels. Edge labels render on the vertical segment. Same-lane connections are fully supported; you do not need to restructure rows or merge nodes to avoid them.

Sidestep side (left vs right) is chosen automatically by the midpoint of the source/destination columns: connections living mostly on the left half of the canvas route through the left margin, the rest through the right. Each sidestep gets its own X channel inside the margin; the margin widens automatically to accommodate multiple sidesteps per side.

Use sidestep connections sparingly. They look visually distinct (longer paths, longer journey for the eye) which honestly conveys "this skips the middle layer." If you have many row 0↔2 connections, consider whether row 1 should mediate them in reality.

## Preset lane keys (colours auto-applied)

| Key        | Label default      | Colour theme  |
|------------|--------------------|---------------|
| `sf`       | Salesforce         | Indigo        |
| `bench`    | Bench App          | Green         |
| `hubspot`  | HubSpot            | Orange        |
| `zapier`   | Zapier             | Purple        |
| `gcp`      | GCP Platform       | Sky blue      |
| `aws`      | AWS                | Amber         |
| `azure`    | Azure              | Blue          |
| `stripe`   | Stripe             | Pink          |
| `qbo`      | QuickBooks Online  | Yellow        |
| `postgres` | PostgreSQL         | Steel blue    |
| `redis`    | Redis              | Red           |
| `kafka`    | Kafka              | Deep purple   |
| `okta`     | Okta               | Amber         |
| `slack`    | Slack              | Violet        |
| `twilio`   | Twilio             | Rose          |
| `ses`      | Amazon SES         | Deep orange   |
| `docusign` | DocuSign           | Slate         |
| `saas`     | Third-Party SaaS   | Cool grey     |
| `generic`  | (any)              | Neutral grey  |

For any key not in this list, supply explicit `bg` / `border` / `header_bg`.

## Node styling

By default, all nodes use a consistent light gray background (`#f1f5f9`) regardless of status. This keeps focus on the structure and connections rather than lifecycle state.

To enable legacy per-status node coloring, set `"node_status_colors": true`:

```python
config = {
    "node_status_colors": true,  # nodes colored by status (cyan=new, amber=transitioning, etc.)
    # ...
}
```

### Status values

| Status        | Meaning                              | Legacy colour |
|---------------|--------------------------------------|---------------|
| `existing`    | Unchanged, currently live            | Slate         |
| `new`         | Being built / not yet live           | Cyan          |
| `transitioning` | Partially moved / dual-write       | Amber         |
| `retiring`    | Being decommissioned                 | Red           |
| `operational` | Fully live on new platform           | Green         |
| `readonly`    | Still present but no writes          | Light grey    |

## Legend

The bottom legend is hidden by default (since node colors are uniform). Configure it with the `legend` key:

```python
# Hide legend (default when node_status_colors=false)
"legend": false

# Show legacy status legend (automatic when node_status_colors=true)
"legend": "default"

# Custom legend for diagram-specific meanings
"legend": [
    ("#0891B2", "API endpoint"),
    ("#B45309", "Background job"),
    ("#15803D", "Database", "Stores persistent state"),  # optional tooltip
]
```

Custom legend entries: `(color, label)` or `(color, label, tooltip_description)`.

## Technology icons

Nodes automatically display technology icons when the engine detects a known technology in the label. Icons appear to the left of each node label in both the node itself and the lane header strip.

### Auto-detection

The engine auto-detects icons from node labels using:
- File extensions: `.py` → Python, `.js` → JavaScript, `.ts` → TypeScript, `.sh` → Bash
- Keywords: "postgres" → PostgreSQL, "redis" → Redis, "kafka" → Kafka, "slack" → Slack
- Exact matches: "Docker", "Kubernetes", "React", "Vue.js", etc.
- Slug derivation: lowercased label with non-alphanumeric chars stripped, matched against 221 built-in icons

### Disabling icons

Set `"show_icons": false` in the config to hide all icons:

```python
config = {
    "title": "My Diagram",
    "show_icons": false,
    # ... rest of config
}
```

### Explicit icon override

To force a specific icon (or suppress auto-detection for one node), use the 5-element node format:

```python
["My Service", "new", "", True, "docker"]    # force Docker icon
["Legacy App", "retiring", "", False, ""]    # suppress icon for this node
```

### Adding missing icons

If a technology icon is missing, use `/get-icon <technology>` to find and add it. The skill searches Simple Icons, Iconify, and the web, then outputs ready-to-paste entries for `svg_engine.py`.

### Common built-in icons

| Category | Examples |
|----------|----------|
| Languages | python, javascript, typescript, go, rust, java, ruby, swift, kotlin |
| Frameworks | react, vuedotjs, angular, nextdotjs, django, rails, spring, flask |
| Databases | postgresql, mysql, mongodb, redis, elasticsearch, sqlite, cockroachdb |
| Cloud | googlecloud, amazonaws, microsoftazure, vercel, netlify, heroku |
| DevOps | docker, kubernetes, terraform, ansible, jenkins, gitlab, github |
| Messaging | slack, discord, twilio, telegram |
| Analytics | amplitude, segment, datadog, newrelic, sentry |

The full list is in `svg_engine.py` under `TECH_ICONS` (221 icons).

## Tips

- Keep edge labels short (3-5 words). They render at 9 px inside a white pill.
- Connections with no meaningful label can pass an empty string `""`.
- If you need more than 6 columns, set `"num_cols": 8` (or higher); column widths shrink proportionally.
- The engine handles the routing automatically; you only need to specify which node connects to which.
- When creating diagrams, prefer using technology names that match built-in icons (e.g., "PostgreSQL" not "Main DB") for visual consistency.
- Save the final SVG to the user's workspace folder, then present it with `mcp__cowork__present_files` (Cowork) or tell the user the file path (Claude Code).

