# How Archify Automatic Port Spread Works: A Deep Dive Into Automatic Port Distribution

> Discover how Archify Automatic Port Spread distributes ports for clear connections. Learn to keep routed edges distinct without manual anchors in this deep dive.

- Repository: [tt-a1i/archify](https://github.com/tt-a1i/archify)
- Tags: deep-dive
- Published: 2026-08-07

---

**Archify Automatic Port Spread automatically distributes connection ports on a shared source component when multiple relationships leave the same side, keeping automatically-routed edges visually distinct without requiring manual anchor configuration.**

The **Archify Automatic Port Spread** feature eliminates visual clutter in diagrams by calculating symmetrical, evenly-spaced port positions for edges that share a common source or target. This behavior is built into the renderer and activates by default for supported diagram types, ensuring clean orthogonal routing while respecting the 8 px/16 px visual rhythm rules defined in the codebase.

## When Automatic Port Spread Activates

The renderer applies this feature selectively based on diagram type and edge configuration. According to the authoring contract in [`archify/references/authoring-contract.md`](https://github.com/tt-a1i/archify/blob/main/archify/references/authoring-contract.md), it runs for **architecture**, **workflow**, **data-flow**, and **lifecycle** diagrams, and is explicitly skipped in these cases:

- Single-relationship edges (only one connection from a component side)
- Sequence diagrams
- Any edge defining `via`, `channelX`, `channelY`, or `labelAt`
- Edges with non-`auto` route specifications

The rule is documented at line 38: "Automatic Port Spread is a default renderer behavior for architecture, workflow, data-flow, and lifecycle diagrams… It does not apply to … explicit `via`, `channelX`, `channelY`, `labelAt`, or non-`auto` routes."

## Core Algorithm Implementation

The spread logic lives in `archify/renderers/shared/geometry.mjs`. The function `automaticPortSpread(relations, boxes, { gutter = 16, maxSpacing = 14 })` implements the full algorithm (lines 1022-1048).

### Step 1: Group Related Edges

The function first collects all automatically-routed edges sharing the same source-side or target-side (lines 1022-1030):

```javascript
// From geometry.mjs lines 1022-1030
function automaticPortSpread(relations, boxes, options = {}) {
  const { gutter = 16, maxSpacing = 14 } = options;
  // Groups edges by their common source or target side
  const groups = collectEdgeGroups(relations, boxes);
  // Only process groups with 2+ edges
  const multiEdgeGroups = groups.filter(g => g.edges.length > 1);

```

### Step 2: Deterministic Sorting

For each group, edges are sorted by the counterpart's coordinate—vertical position for horizontal sides, horizontal position for vertical sides—ensuring stable, predictable port assignment regardless of input order (lines 1028-1035).

### Step 3: Space Calculation and Spreading

The algorithm measures available `extent` on the component side, reserves the 16 px `gutter` on each edge, and caps spacing at `maxSpacing` (14 px) to prevent excessive spread (lines 1037-1040). Each edge receives a symmetrical offset from the side's midpoint (lines 1042-1048).

## Rhythm Bridge Fallback for Near-Parallel Edges

Before spread calculation, Archify checks for potential violations of its interior rhythm rules. If two ports would result in segments shorter than `interiorSegmentPx` (8 px), the `automaticPortRhythmBridge` function generates an "outside-channel" bridge that respects the rhythm floors (lines 1030-1036 in `geometry.mjs`). This preprocessing guarantees a valid route exists before the main spread logic runs.

## Port Spread Stability Guarantee

The **Archify Automatic Port Spread** algorithm produces deterministic results. The test suite in `archify/test/automatic-port-spread.test.mjs` (lines 63-78) explicitly validates this: "automatic port assignment is stable when relationship input order changes." Authors can refactor diagrams without worrying that relationship declaration order will scramble their layout.

## Practical Examples

### Architecture Diagram Fan-Out

Here's a hub component emitting three connections—the spread automatically assigns distinct ports:

```javascript
import { render } from 'archify/renderers/architecture/render-architecture.mjs';

const doc = {
  schema_version: 1,
  diagram_type: 'architecture',
  meta: { title: 'Automatic Port Spread Demo' },
  components: [
    { id: 'hub',   type: 'backend',   label: 'Hub',   pos: [100, 280], size: [120, 60] },
    { id: 'upper', type: 'external',  label: 'Upper', pos: [500, 100], size: [120, 60] },
    { id: 'middle',type: 'database',  label: 'Middle',pos: [500, 280], size: [120, 60] },
    { id: 'lower', type: 'cloud',     label: 'Lower', pos: [500, 460], size: [120, 60] },
  ],
  connections: [
    { id: 'to-upper',  from: 'hub', to: 'upper' },
    { id: 'to-middle', from: 'hub', to: 'middle' },
    { id: 'to-lower',  from: 'hub', to: 'lower' },
  ],
};

render('architecture', doc);   // Ports at y-296, 310, 324 on the hub's right side

```

### Workflow Diagram with Lanes

The same spread logic applies across workflow lanes:

```javascript
import { render } from 'archify/renderers/workflow/render-workflow.mjs';

const doc = {
  schema_version: 1,
  diagram_type: 'workflow',
  meta: { title: 'Workflow Port Spread' },
  lanes: [
    { id: 'upper-lane', label: 'Upper' },
    { id: 'hub-lane',   label: 'Hub'   },
    { id: 'lower-lane', label: 'Lower' },
  ],
  nodes: [
    { id: 'hub',   lane: 'hub-lane',   col: 0, type: 'backend', label: 'Hub' },
    { id: 'upper', lane: 'upper-lane', col: 3, type: 'external', label: 'Upper' },
    { id: 'middle',lane: 'hub-lane',   col: 3, type: 'database', label: 'Middle' },
    { id: 'lower', lane: 'lower-lane', col: 3, type: 'cloud',    label: 'Lower' },
  ],
  edges: [
    { id: 'to-upper',  from: 'hub', to: 'upper' },
    { id: 'to-middle', from: 'hub', to: 'middle' },
    { id: 'to-lower',  from: 'hub', to: 'lower' },
  ],
};

render('workflow', doc);   // Ports spread vertically: y-233, 243, 253

```

## Configuration Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `gutter` | 16 px | Reserved space from component corners |
| `maxSpacing` | 14 px | Maximum distance between adjacent ports |

These values are hardcoded in `geometry.mjs` but passed through the options object for potential future customization.

## Summary

- **Archify Automatic Port Spread** runs automatically for architecture, workflow, data-flow, and lifecycle diagrams with multiple edges sharing a component side.
- The algorithm in `automaticPortSpread()` guarantees **symmetrical, deterministic port placement** regardless of input order.
- A **rhythm bridge fallback** prevents sub-8 px segments that would violate visual rhythm rules.
- Authors retain full control via explicit `via`, `channelX`, `channelY`, `labelAt`, or non-`auto` route specifications—the spread only affects automatically routed edges.

## Frequently Asked Questions

### How is port spread order determined when edges connect to targets at different positions?

The algorithm sorts edges by their counterpart's coordinate before calculating offsets. For a right-side source port, targets are sorted by vertical position; for a top-side port, by horizontal position. This ensures the visual order matches the diagram's spatial layout as implemented in `geometry.mjs` lines 1028-1035.

### Can I disable Automatic Port Spread for specific edges?

Yes. Define any explicit routing control—`via` points, `channelX`, `channelY`, `labelAt` positioning, or set `route` to a non-`auto` value—and the renderer will skip spread calculation for that edge, using your specified anchors instead. This is documented in the authoring contract at line 38.

### Why 16 px gutters and 14 px max spacing?

These values align with Archify's broader **8 px/16 px visual rhythm system**. The 16 px gutter preserves corner clearance for component styling, while 14 px max spacing prevents excessive spread that would create awkwardly long connector starts. The algorithm falls back to tighter spacing if available `extent` is limited, always maintaining rhythm-compliant segment lengths.

### Does port spread affect diagram compilation performance?

No meaningful impact. The `automaticPortSpread` function operates on already-collected edge groups during the geometry preparation phase. The sorting and offset calculations are O(n log n) per side-group where n is the edge count—typically small. The test suite validates performance stability across edge reordering scenarios in `automatic-port-spread.test.mjs`.