# ArchitectureFigure

A component map: services, datastores, trust boundaries, and what talks to what.

- Group: Diagrams
- Import: `import { ArchitectureFigure } from '@misoto22/folio/diagrams'`
- Page: https://ui.misoto22.com/components/architecture-figure/
- Related: dataflow-figure, diagram, diagram-canvas

## When to reach for it

Reach for it when the question is "what talks to what". If the question is "in what order", that is a workflow or a sequence; if it is "what is in this arrow", that is a data flow.

## Anatomy

- **Figure shell** (required) — The frame all five figures sit in — a serif title, the scrolling paper surface, the role="img" svg. What a map hands it is the relationship list: every connection published as "CloudFront → API: HTTPS", which is the sentence this diagram type exists to make.
- **Grid** (required) — What row and col index into: a 184 × 72 cell with 64 and 76 unit gutters, overridable per figure through spec.layout — where cellW is the pitch AND the width of the plates drawn at it. A component declaring neither row nor col flows into the next free cell, wrapping at layout.cols; pos replaces the grid entirely and puts the box at an absolute coordinate.
- **Component plate** (required) — One box per component: a sigil and an eyebrow on the top line, the name at reading size, the sublabel in mono under it. The eyebrow prints the component’s own tag when it has one and the kind word otherwise, and the box grows past a declared height rather than printing through its own bottom rule.
- **Boundary frame** — A labelled rule around the union of the boxes its wraps names, inflated by pad — 28 units by default. Solid for a region, dashed for a security-group, so where a thing runs and what may reach it are two different lines before either label is read.
- **Connections** — Lines that leave and arrive perpendicular to a face, spread across that face when several share it, each with its wording on a mask that punches the line out from under itself.
- **Key** — The kinds actually drawn, each shown with the same sigil the plates carry. legend="all" names all seven instead; legend="hidden" prints none.

## Best practices

### Do

- Build a new spec object when something changes. The model is memoised on the spec’s identity, so mutating spec.components in place leaves the same reference and the figure goes on drawing the picture it was first given — development prints DIAGRAM_SPEC_MUTATED when it catches that, and a production build prints nothing and still draws the old picture.
- Place both ends with pos when a connection carries a hand-tuned via, channelX or channelY. Those are honoured only when both endpoints were placed absolutely — on the grid they are coordinates in a space this renderer did not choose, so a route between two row/col components is dropped and re-routed.
- Drive a guided reading yourself. meta.views typechecks and no renderer reads it — a chapter is a control rather than a layout — so the chapter’s focus ids have to arrive as activeIds, which is what dims everything else and adds the "n highlighted" status line.
- Leave row and col off the components you have no opinion about. They flow into the next free cell in declaration order, wrapping at layout.cols and stepping around whatever the placed ones claimed — a component declaring neither used to mean row 0, column 0, which is one plate with the rest of them underneath it.

### Don’t

- Two components on one cell are still two plates at one coordinate, one drawn over the other: there is no second place to put the second plate. Development prints DIAGRAM_CELL_COLLISION naming both, which is the only signal — the picture is identical either way.
- A boundary silently shrinks to the components it can find. A wraps id no component declares is skipped, and a boundary whose ids are all missing draws no frame at all — a trust boundary can leave the picture while the specification still claims it.

## Accessibility

- The <svg> is role="img" with a name, so a screen reader announces a picture instead of walking two hundred <text> nodes in drawing order.
- The diagram’s content is published beside it as an ordinary list — every node with its kind, every relationship as "A → B: over HTTPS". That list is where the meaning lives for anyone not looking at the picture.
- Passing onSelectNode turns that list into real buttons, which is the keyboard’s only route to a selection: the plates inside the picture are presentational by construction.

## ArchitectureFigure

A component map: services, datastores, boundaries, and what talks to what. Takes the same JSON an archify `architecture` specification carries — `components`, `boundaries`, `connections` — and draws it in this system's own terms: paper plates on a hairline, one reversed plate for the component the diagram is about, and seven drawn sigils where archify uses seven hues. IT RENDERS ON A SERVER because every position is already in the specification. A component gives a `row` and a `col` into a grid whose cell size is either declared or defaulted, or it gives an absolute `pos` — nothing here is solved for, relaxed, or measured. So the markup is a pure function of the input, it is identical on the server and in the browser, and there is no layout shift on hydration because there is no layout to do. A component that declares NEITHER flows: it takes the next free cell in declaration order, wrapping at `layout.cols`. That is still arithmetic on numbers the specification carries rather than a solver — and the alternative was every unplaced component defaulting to row 0, column 0 and stacking into one plate. BOUNDARIES ARE DRAWN FIRST, AND DRAWN DIFFERENTLY. A `region` is where something RUNS — a VPC, a zone, a cluster — and is a solid frame. A `security-group` is what may REACH it, and is dashed. That is not decoration: an infrastructure diagram is very often read for exactly one of those two questions, and a reader should be able to tell which line answers which without reading either label.

### Props

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

Also accepts: `FigureChrome`.

## Example — default

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

<ArchitectureFigure
  spec={{
    meta: { title: 'One request, end to end', subtitle: 'The edge, the service, the row it reads.' },
    components: [
      { id: 'client', type: 'external', label: 'Browser', sublabel: 'Safari / Chrome', row: 0, col: 0 },
      { id: 'edge', type: 'cloud', label: 'CloudFront', sublabel: 'CDN', row: 0, col: 1 },
      { id: 'api', type: 'backend', label: 'API', sublabel: 'FastAPI', row: 0, col: 2, tag: ':8000' },
      { id: 'cache', type: 'database', label: 'Redis', sublabel: 'read-through', row: 1, col: 2 },
      { id: 'db', type: 'database', label: 'Postgres', sublabel: 'primary', row: 0, col: 3 },
    ],
    boundaries: [{ kind: 'region', label: 'ap-southeast-2', wraps: ['api', 'cache', 'db'] }],
    connections: [
      { id: 'a', from: 'client', to: 'edge', label: 'HTTPS', variant: 'emphasis' },
      { id: 'b', from: 'edge', to: 'api' },
      { id: 'c', from: 'api', to: 'cache', label: 'read-through' },
      { id: 'd', from: 'api', to: 'db', label: 'SQL' },
    ],
    cards: [
      { dot: 'cyan', title: 'Edge', items: ['Every request is fronted by the CDN.'] },
      { dot: 'emerald', title: 'Application', items: ['Reads go through Redis first.', 'Postgres is the source of truth.'] },
    ],
  }}
/>
```

## Example — two kinds of boundary

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

<ArchitectureFigure
  spec={{
    meta: { title: 'An inbound webhook', subtitle: 'Where it runs, and what is allowed to reach it.' },
    components: [
      { id: 'partner', type: 'external', label: 'Partner', sublabel: 'webhook sender', row: 0, col: 0 },
      { id: 'gateway', type: 'security', label: 'Gateway', sublabel: 'signature check', row: 0, col: 1, tag: 'mTLS' },
      { id: 'queue', type: 'messagebus', label: 'Ingest queue', sublabel: 'SQS', row: 0, col: 2 },
      { id: 'worker', type: 'backend', label: 'Worker', sublabel: 'consumer', row: 0, col: 3 },
    ],
    boundaries: [
      { kind: 'region', label: 'ap-southeast-2', wraps: ['gateway', 'queue', 'worker'], pad: 38 },
      { kind: 'security-group', label: 'Reachable only from the gateway', wraps: ['queue', 'worker'], pad: 16 },
    ],
    connections: [
      { id: 'a', from: 'partner', to: 'gateway', label: 'signed POST' },
      { id: 'b', from: 'gateway', to: 'queue', label: 'verified', variant: 'security' },
      { id: 'c', from: 'queue', to: 'worker' },
    ],
  }}
/>
```

## Example — a denser map

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

<ArchitectureFigure
  spec={{
    meta: { title: 'The platform', subtitle: 'Eight services, and one path through them.' },
    layout: { gapX: 58, gapY: 58 },
    components: [
      { id: 'web', type: 'frontend', label: 'Web', sublabel: 'Next.js', row: 0, col: 0 },
      { id: 'mobile', type: 'frontend', label: 'Mobile', sublabel: 'iOS', row: 1, col: 0 },
      { id: 'gw', type: 'backend', label: 'Gateway', sublabel: 'routing', row: 0, col: 1 },
      { id: 'auth', type: 'security', label: 'Auth', sublabel: 'OIDC', row: 1, col: 1 },
      { id: 'orders', type: 'backend', label: 'Orders', sublabel: 'service', row: 0, col: 2 },
      { id: 'search', type: 'backend', label: 'Search', sublabel: 'service', row: 1, col: 2 },
      { id: 'db', type: 'database', label: 'Postgres', sublabel: 'primary', row: 0, col: 3 },
      { id: 'index', type: 'database', label: 'OpenSearch', sublabel: 'index', row: 1, col: 3 },
    ],
    connections: [
      { id: 'a', from: 'web', to: 'gw' },
      { id: 'b', from: 'mobile', to: 'gw', fromSide: 'right', toSide: 'left' },
      { id: 'c', from: 'gw', to: 'auth', label: 'verify' },
      { id: 'd', from: 'gw', to: 'orders', label: 'REST', variant: 'emphasis' },
      { id: 'e', from: 'gw', to: 'search', label: 'REST' },
      { id: 'f', from: 'orders', to: 'db', label: 'SQL', variant: 'emphasis' },
      { id: 'g', from: 'search', to: 'index', label: 'query' },
      { id: 'h', from: 'orders', to: 'index', label: 'reindex', variant: 'dashed' },
    ],
    cards: [
      { dot: 'cyan', title: 'Checkout', items: ['Gateway to Orders to Postgres carries every paid order.'] },
      { dot: 'slate', title: 'Coupling', items: ['Orders writes the index Search reads.', 'Only Orders and Search reach a datastore at all.'] },
    ],
  }}
/>
```

## Example — placed by hand

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

<ArchitectureFigure
  spec={{
    meta: { title: 'Orders, with the retry drawn under the row', subtitle: 'Every box placed by hand, so the loop can be too.' },
    components: [
      { id: 'api', type: 'backend', label: 'Checkout API', sublabel: 'Fastify', pos: [40, 40] },
      { id: 'queue', type: 'messagebus', label: 'SQS orders', sublabel: 'standard queue', pos: [300, 40] },
      { id: 'worker', type: 'backend', label: 'Fulfilment worker', sublabel: 'ECS service', pos: [560, 40] },
      { id: 'dlq', type: 'messagebus', label: 'orders-dlq', sublabel: 'paged on depth > 0', pos: [820, 40] },
    ],
    connections: [
      { id: 'a', from: 'api', to: 'queue', label: 'publish' },
      { id: 'b', from: 'queue', to: 'worker', label: 'consume', variant: 'emphasis' },
      { id: 'c', from: 'worker', to: 'dlq', label: 'after 5 attempts', variant: 'dashed' },
      {
        id: 'retry',
        from: 'worker',
        to: 'queue',
        label: 'retry in 30s',
        variant: 'dashed',
        fromSide: 'bottom',
        toSide: 'bottom',
        via: [
          [652, 180],
          [392, 180],
        ],
      },
    ],
  }}
/>
```
