How the Sequence Renderer Manages Participants, Segments, and Messages in Archify

The Sequence renderer transforms a declarative JSON model into a validated SVG by materializing participants, segments, and messages into geometry objects, then rendering them in a strict z-order that places participants on top.

The archify/renderers/sequence/render-sequence.mjs module implements a three-stage pipeline—materialization, validation, and rendering—that converts abstract sequence diagram definitions into production-ready SVG output. This article examines the internal mechanics of how the renderer handles the three core data types: participants, segments, and messages.

Materialising Participants into Geometry Objects

The renderer first converts raw participant definitions into fully-computed geometry objects stored in a Map for O(1) lookup during message routing.

const participants = new Map(
  asArray(sequence.participants).map((participant, index) => [
    participant.id,
    {
      ...participant,
      index,
      cx: participantX(index),
      x: participantX(index) - layout.participantW / 2,
      y: layout.topY,
      width: layout.participantW,
      height: layout.participantH,
      cy: layout.topY + layout.participantH / 2,
    },
  ])
);

Each participant receives:

  • cx: Horizontal center (used for message start/end points)
  • x, y: Top-left corner for the rendered box
  • cy: Vertical center of the participant header
  • Computed dimensions: Pulled from layout.participantW and layout.participantH

The helper participantX(index) (lines 62-64) calculates horizontal placement using layout.leftX + index * layout.colGap, distributing participants evenly across the canvas width.

Building Segment Frames for Visual Grouping

Segments define vertical bands that group related messages into logical phases. The renderer transforms each segment into a frame object with explicit geometry:

const compositionFrames = asArray(sequence.segments).map((segment, index) => ({
  id: index,
  label: segment.label,
  kind: 'segment',
  x: 48,
  y: segment.from,
  width: viewBox[0] - 96,
  height: segment.to - segment.from,
  radius: 10,
}));

Key properties:

Property Purpose
y / height Vertical span from segment.from to segment.to
width: viewBox[0] - 96 Full canvas width minus 48px margins on each side
radius: 10 Corner rounding for the background rectangle

These frames serve dual purposes: visual backdrop (rendered via renderSegment at lines 20-22) and collision boundaries for label placement algorithms.

Computing Message Geometry and Routing

Message rendering depends on precise geometric calculations that connect participant centers with directional awareness:

function messageGeometry(message) {
  const from = participants.get(message.from);
  const to   = participants.get(message.to);
  if (!from || !to || typeof message.y !== 'number') return null;
  const direction = to.cx > from.cx ? 1 : -1;
  const start = from.cx + direction * 7;
  const end   = to.cx - direction * 7;
  return { start, end, center: (start + end) / 2 };
}

The computation proceeds as follows:

  1. Lookup: Resolve message.from and message.to against the participant Map
  2. Validation: Ensure both participants exist and message.y is numeric
  3. Direction: Determine left-to-right (+1) or right-to-left (-1) flow
  4. Inset: Apply 7px padding from participant edges to avoid visual collision
  5. Center: Pre-compute midpoint for label placement

The vertical position (message.y) originates from the source JSON and is validated to fall within the readable timeline bounds (lines 61-68).

Deterministic Rendering Order in renderSvg()

The renderSvg() function (lines 13-44) assembles the final SVG through a carefully sequenced series of calls that establish proper visual layering:

  1. Background grid — Static reference lines
  2. renderSegment — Segment frames as shaded backgrounds
  3. renderLifeline — Vertical dashed lines extending from participants
  4. renderActivation — Execution bars showing active periods
  5. renderMessage — Arrows and labels using messageGeometry() and messageLabel()
  6. renderSegmentLabel — Phase labels with collision avoidance
  7. renderParticipant — Participant boxes, sublabels, and brand marks
  8. renderLegend — Optional key/explanation block

Critical design decision: Participants render last so they visually override lifelines and message endpoints, matching standard sequence diagram conventions where participant headers appear "in front" of connecting lines.

Validation Pipeline Before SVG Generation

The validateSequence() function (lines 35-91) executes a comprehensive suite of checks that prevent malformed output:

  • Identity: Unique participant IDs across the diagram
  • Space: Minimum 120px between lifelineBottom and lifelineTop
  • Fit: Participant labels must fit within computed box widths
  • Legibility: availableNodeTextWidth ensures sublabel readability
  • Proximity: Message endpoints must be ≥ 60px from participant centers
  • Collision: cleanFlowProblems and cleanCrossingProblems detect arrow overlaps, label intersections, and segment frame conflicts

Failed validations trigger throwDiagnosticProblems, which aborts rendering and returns a detailed error list rather than producing broken SVG.

Complete Input-to-Output Example

This minimal JSON demonstrates the full data model:

{
  "participants": [
    { "id": "alice", "label": "Alice", "type": "external" },
    { "id": "bob",   "label": "Bob",   "type": "external" }
  ],
  "messages": [
    { "from": "alice", "to": "bob", "label": "Hello", "y": 200 }
  ],
  "segments": [
    { "label": "Login Flow", "from": 150, "to": 300 }
  ]
}

Excerpted SVG output:

<svg viewBox="0 0 920 760" …>
  <rect x="48" y="150" width="824" height="150" rx="10" class="c-lane"/>
  <path d="M 150 200 L 770 200" class="a-default" …/>
  <text x="460" y="190" class="t-backend" font-size="9" text-anchor="middle">Hello</text>
  <g …>
    <rect x="62" y="72" width="86" height="54" rx="6" class="c-mask"/>
    <rect … class="c-external"/>
    <text …>Alice</text>
  </g>
</svg>

The segment renders as a background rectangle, the message as a horizontal path with centered label, and participants as layered groups with masking rectangles to ensure clean edges.

Source Code Architecture

File Responsibility
archify/renderers/sequence/render-sequence.mjs Core pipeline: materialization, validation, SVG assembly
archify/shared/geometry.mjs Collision detection (rectsOverlap, cleanFlowProblems)
archify/shared/utils.mjs Utilities (asArray, textUnits, esc)
archify/shared/cli.mjs SVG serialization (svgRootAttrs, writeDiagram)
archify/schemas/sequence.schema.json JSON Schema for input validation

Summary

  • Materialization converts abstract JSON entities into geometry objects with computed coordinates
  • Participants store center points (cx, cy) that enable efficient message routing
  • Segments become frame objects with explicit bounds for background rendering and collision detection
  • Messages derive horizontal endpoints from participant geometry plus directional insets
  • Validation enforces spatial constraints and prevents visual collisions before any SVG is generated
  • Rendering order places participants last in the z-stack to achieve conventional diagram aesthetics

Frequently Asked Questions

What data structure stores participants during rendering?

A JavaScript Map keyed by participant ID. This provides O(1) lookup when resolving message.from and message.to references during message geometry computation.

How does the renderer prevent message arrows from overlapping participant boxes?

The messageGeometry() function applies a 7-pixel inset using direction * 7 on both ends, ensuring arrows start and stop slightly inside the participant center lines rather than at exact edges.

Why are participants rendered after messages in the SVG output?

The renderSvg() sequence (lines 13-44) deliberately places renderParticipant near the end so participant headers visually cover lifeline extensions and message endpoints. This matches standard sequence diagram conventions where participant boxes appear "on top."

What happens if validation fails in validateSequence()?

The renderer calls throwDiagnosticProblems, which aborts execution and returns a structured list of errors rather than generating malformed SVG. Checks include unique IDs, minimum spacing, label fit, and collision detection between arrows, labels, and segment frames.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →