# SequenceFigure

A call chain over time: who asks whom, in what order, and what comes back.

- Group: Diagrams
- Import: `import { SequenceFigure } from '@misoto22/folio/diagrams'`
- Page: https://ui.misoto22.com/components/sequence-figure/
- Related: workflow-figure, architecture-figure

## When to reach for it

The only figure whose vertical axis means something. A message carries an explicit y, so two calls eight units apart happened together and two two hundred apart did not.

## Anatomy

- **Figure shell** (required) — The frame around the picture. What a trace hands it is a message list, and it is published in the order the messages array is written — which is the only ordering a reader who cannot see the axis is given.
- **Participant head** (required) — A 148-unit plate per participant at the top of its column, in the order the participants array puts them. meta.column_fit: "spread" widens every column to the widest name instead of holding the fixed width.
- **Lifeline** (required) — A hairline dropped from each head to below the last thing on the axis. A hairline and not a rule, because it is the axis: seven lifelines at message weight is seven vertical lines competing with twelve horizontal ones.
- **Message** (required) — A horizontal line at its own y, inset from both lifelines so the arrowhead lands clear of the axis, with the wording on a mask above it. A return is dashed AND takes an open head — two signals, because the reply is what a reader picks out of a dense trace.
- **Activation bar** — A narrow bar on a lifeline from one y to another: who is BUSY. It is the fact a sequence diagram carries that a list of calls does not, and one bar overlapping another is the reason to draw the picture at all.
- **Segment band** — A dashed rule across the whole figure with a mono caption on a masked patch — request, fallback, response. A caption rather than a tinted panel, because a second ground inside the figure would sit under every message label’s mask.

## Best practices

### Do

- Author the messages in ascending y. The picture reads the axis and the summary list beside it iterates the array, so a message written out of order is drawn at one point in the exchange and read aloud at another.
- Reach for meta.column_fit: "spread" when a participant’s name is longer than the fixed column. The plate wraps to two lines and ellipsises whatever still does not fit, and shortening the name to make it fit is not a repair — the name is the data.
- Space the messages by what actually happened. Nothing normalises y, so eight units between two calls reads as together and two hundred reads as a wait; an evenly spaced list is tidier and says something the trace does not.

### Don’t

- A message naming a participant that participants does not declare leaves the picture and the summary list together — there is no column to draw it between, and a text equivalent reporting a call the picture cannot show is two halves of one figure disagreeing. Development prints DIAGRAM_EDGE_DANGLING with both ends, which is now the only place that message is reported at all.
- This figure ASSERTS order. Three calls a service makes concurrently, given three y values because they had to be given something, are a picture claiming the second waited for the first — an activation bar or a segment caption is where "these overlap" belongs.

## Accessibility

- Return messages are dashed AND take an open arrowhead — two signals, because the reply is what a reader most often needs to pick out of a dense trace.
- The message list beside the picture reads in order, which is the same order the axis is drawn in.

## SequenceFigure

A call chain over time: who asks whom, in what order, and what comes back. The one diagram type here whose vertical axis MEANS something. Every other figure's y is layout; a sequence's y is time, which is why a message carries an explicit `y` rather than an index — two calls 8 units apart happened together, and two 200 apart did not, and an evenly spaced list of messages would erase that distinction while looking tidier. **Lifelines** are hairlines, not solid rules, because they are the axis rather than the content. A lifeline drawn at the weight of a message is a diagram where seven vertical lines compete with twelve horizontal ones. **Activation bars** say who is BUSY, which is the fact a sequence diagram carries that a list of calls does not — the third participant's bar overlapping the second's is the reason to draw them at all. **Segments** band the axis into phases — request, fallback, response — printed as a rule with a mono caption rather than as a tinted panel, for the same reason architecture boundaries are: a second ground inside the figure would sit under every message label's mask. A `return` message is dashed AND takes an open arrowhead. Two signals rather than one, because the reply is the thing a reader most often needs to pick out of a dense trace, and a dash alone is doing the same work as `dashed` already does for an asynchronous call.

### Props

- `spec` (required) — `SequenceSpec`.

Also accepts: `FigureChrome`.

## Example — default

```tsx
import { SequenceFigure } from '@misoto22/folio/diagrams'

<SequenceFigure
  spec={{
    meta: { title: 'A cache miss', subtitle: 'The vertical axis is time, not layout.' },
    participants: [
      { id: 'web', type: 'frontend', label: 'Web app', sublabel: 'React' },
      { id: 'api', type: 'backend', label: 'API', sublabel: 'handler' },
      { id: 'cache', type: 'database', label: 'Redis' },
      { id: 'db', type: 'database', label: 'Postgres' },
    ],
    segments: [
      { from: 130, to: 250, label: 'Request' },
      { from: 260, to: 380, label: 'Fallback' },
    ],
    messages: [
      { id: 'get', from: 'web', to: 'api', y: 160, label: 'GET /me', variant: 'emphasis' },
      { id: 'read', from: 'api', to: 'cache', y: 200, label: 'read' },
      { id: 'miss', from: 'cache', to: 'api', y: 235, label: 'miss', variant: 'return' },
      { id: 'query', from: 'api', to: 'db', y: 285, label: 'select', variant: 'emphasis' },
      { id: 'rows', from: 'db', to: 'api', y: 320, label: 'rows', variant: 'return' },
      { id: 'json', from: 'api', to: 'web', y: 360, label: '200 JSON', variant: 'return' },
    ],
    activations: [
      { participant: 'api', from: 155, to: 365, type: 'backend' },
      { participant: 'cache', from: 195, to: 240, type: 'database' },
      { participant: 'db', from: 280, to: 325, type: 'database' },
    ],
  }}
/>
```

## Example — an asynchronous hop

```tsx
import { SequenceFigure } from '@misoto22/folio/diagrams'

<SequenceFigure
  spec={{
    meta: {
      title: 'Webhook delivery',
      subtitle: 'A queue absorbs the retry the caller never waits for.',
      column_fit: 'spread',
    },
    participants: [
      { id: 'billing', type: 'backend', label: 'Billing', sublabel: 'invoice writer' },
      { id: 'outbox', type: 'messagebus', label: 'Outbox', sublabel: 'delivery queue' },
      { id: 'partner', type: 'external', label: 'Partner webhook endpoint', sublabel: 'customer-operated' },
    ],
    segments: [{ from: 262, to: 340, label: 'Retry' }],
    messages: [
      { id: 'enq', from: 'billing', to: 'outbox', y: 150, label: 'enqueue', variant: 'dashed' },
      { id: 'post1', from: 'outbox', to: 'partner', y: 195, label: 'POST /hooks', variant: 'security' },
      { id: 'fail', from: 'partner', to: 'outbox', y: 230, label: '503', variant: 'return' },
      { id: 'post2', from: 'outbox', to: 'partner', y: 290, label: 'attempt 2', variant: 'security' },
      { id: 'ok', from: 'partner', to: 'outbox', y: 325, label: '200', variant: 'return' },
    ],
    activations: [{ participant: 'outbox', from: 145, to: 335, type: 'messagebus' }],
  }}
/>
```

## Example — time as the axis

```tsx
import { SequenceFigure } from '@misoto22/folio/diagrams'

<SequenceFigure
  spec={{
    meta: { title: 'A slow dependency', subtitle: 'Three calls in 24 units, then 186 of waiting.' },
    participants: [
      { id: 'gw', type: 'backend', label: 'Gateway' },
      { id: 'search', type: 'backend', label: 'Search' },
      { id: 'rank', type: 'backend', label: 'Ranker' },
      { id: 'vendor', type: 'external', label: 'Pricing vendor' },
    ],
    messages: [
      { id: 'q', from: 'gw', to: 'search', y: 150, label: 'query' },
      { id: 'f', from: 'gw', to: 'rank', y: 162, label: 'features' },
      { id: 'p', from: 'gw', to: 'vendor', y: 174, label: 'quote', variant: 'emphasis' },
      { id: 'hits', from: 'search', to: 'gw', y: 205, label: 'hits', variant: 'return' },
      { id: 'scores', from: 'rank', to: 'gw', y: 225, label: 'scores', variant: 'return' },
      { id: 'price', from: 'vendor', to: 'gw', y: 360, label: 'price', variant: 'return' },
    ],
    activations: [
      { participant: 'gw', from: 145, to: 368, type: 'backend' },
      { participant: 'search', from: 155, to: 210, type: 'backend' },
      { participant: 'rank', from: 167, to: 230, type: 'backend' },
      { participant: 'vendor', from: 179, to: 365, type: 'external' },
    ],
    cards: [
      { dot: 'rose', title: 'The tail', items: ['Search and Ranker both answer within 75 units of the first call.', 'The vendor holds its request open for 186.'] },
    ],
  }}
/>
```

## Example — names that do not fit

```tsx
import { SequenceFigure } from '@misoto22/folio/diagrams'

<SequenceFigure
  spec={{
    meta: {
      title: 'Signing in to the dealer portal',
      subtitle: 'Every column as wide as the longest real name.',
      column_fit: 'spread',
    },
    participants: [
      { id: 'browser', type: 'frontend', label: 'Dealer portal (browser)', sublabel: 'Next.js' },
      { id: 'api', type: 'backend', label: 'dealer-portal-api', sublabel: 'Django + DRF' },
      { id: 'db', type: 'database', label: 'SQL Server 2025', sublabel: 'legacy schema' },
    ],
    messages: [
      { id: 'post', from: 'browser', to: 'api', y: 160, label: 'POST /api/v1/token/', variant: 'emphasis' },
      { id: 'lookup', from: 'api', to: 'db', y: 200, label: 'SELECT dealer WHERE email = ?' },
      { id: 'row', from: 'db', to: 'api', y: 240, label: 'one row', variant: 'return' },
      { id: 'pair', from: 'api', to: 'browser', y: 282, label: 'access + refresh', variant: 'return' },
    ],
    activations: [
      { participant: 'api', from: 168, to: 288, type: 'backend' },
      { participant: 'db', from: 196, to: 246, type: 'database' },
    ],
  }}
/>
```
