# Specs and live models

A spec is plain data that describes your diagram's intent; a live model is the identity-bearing object that holds that data while the diagram runs.

Follow a spec update through reconciliation to see what changes and which live objects retain their identity; see [Introduction](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/introduction) for the model, engine and instance entry points.

## From intent to live objects

The shared input layer converts specs into models and reconciles subsequent spec lists against those models. You do not need to construct engine objects to describe a flow.

```mermaid
flowchart LR
  S["Plain specs"] --> B["Framework binding or renderer instance"]
  B --> M["Live diagram model"]
  E["Engine: commands, layout, validation"] --> M
  M --> R["Renderer: visible geometry"]
  B --> R
```

| Intent you supply | Live object | What it holds |
| --- | --- | --- |
| [`NodeSpec`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-renderer-instance-nodespec) | [`NodeModel`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-engine-models-nodemodel) | Type, position, size, payload, metadata and ports |
| [`PortSpec`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-renderer-instance-portspec) | [`PortModel`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-engine-models-portmodel) | Direction, side, glyph, data type and connection constraints |
| [`EdgeSpec`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-renderer-instance-edgespec) | [`LinkModel`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-engine-models-linkmodel) | Endpoints, routing, connector, labels and bends |
| [`GroupSpec`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-renderer-instance) | [`GroupModel`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-engine-models-groupmodel) | Membership and frame geometry |

Give nodes and edges explicit, stable `id` values when you intend to update them. For a plain spec with an existing id, reconciliation updates the existing object rather than constructing another one. Entries without ids receive `node-<index>` or `edge-<index>` ids, so their identity depends on their position in the list.

## Payload and metadata have different jobs

Put application payload in `data`, such as an order status or domain identifier. Put diagram-adjacent settings in `metadata`. The node's top-level `label`, `sublabel` and `shape` fields are conveniences for `metadata.label`, `metadata.sublabel` and `metadata.shape`; they are not writes to `data`.

On the node update path, a supplied `data` object replaces the payload dictionary. Metadata entries are applied by key. An omitted `position` leaves an existing node where it is, and an omitted `selected` leaves the user's selection alone.

## Describe a flow, then query its live identity

Run this module in the browser. It creates a sized container and mounts the flow with [`render()`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-element-core). It supplies two boxes, a labeled connection and a fitted zone, then changes the first box's label without replacing its live node.

Install the packages the module imports:

```bash
npm install @grafloria/element @grafloria/renderer @grafloria/engine
```

```ts title="configure-flow.ts"
import { render } from '@grafloria/element';
import type {
  EdgeSpec,
  GroupSpec,
  NodeSpec,
  PortSpec,
} from '@grafloria/renderer';

export function configureFlow() {
  const output: PortSpec = {
    id: 'intake-out', side: 'right', type: 'output',
  };
  const input: PortSpec = {
    id: 'review-in', side: 'left', type: 'input',
  };
  const nodes: NodeSpec[] = [
    {
      id: 'intake', label: 'Intake',
      position: { x: 80, y: 100 },
      size: { width: 150, height: 60 },
      data: { orderId: 'order-42', status: 'received' },
      metadata: { domain: 'orders' },
      ports: [output],
    },
    {
      id: 'review', label: 'Review',
      position: { x: 360, y: 100 },
      size: { width: 150, height: 60 },
      ports: [input],
    },
  ];
  const edges: EdgeSpec[] = [{
    id: 'handoff', source: 'intake', target: 'review',
    sourceHandle: 'intake-out', targetHandle: 'review-in',
    type: 'orthogonal', label: 'submit',
  }];
  const groups: GroupSpec[] = [{
    id: 'stage', label: 'Order processing',
    children: ['intake', 'review'], padding: 30,
    style: { fill: '#f3f4f6', stroke: '#d7dbe0' },
  }];

  const container = document.createElement('div');
  container.style.height = '400px';
  document.body.appendChild(container);
  const instance = render({ nodes, edges, groups }, container);

  const model = instance.getModel();
  const intakeBefore = model.getNode('intake');
  instance.setNodes(nodes.map(node =>
    node.id === 'intake' ? { ...node, label: 'Received' } : node
  ));
  instance.fitView();

  return {
    sameNode: intakeBefore !== undefined &&
      intakeBefore === model.getNode('intake'),
    outputPort: model.getPortById('intake-out'),
    handoff: model.getLink('handoff'),
    intakeIsMember: model.getGroup('stage')?.members.has('intake') ?? false,
  };
}

const result = configureFlow();
console.log(result);
```

![Received connects to Review through the submit arrow inside the Order processing zone.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/89bdcc2c9adf882e959bb22ce19e4eb8.png)

The container has a height of `400px`. The resulting diagram shows **Received** connected to **Review** inside **Order processing**. The returned `sameNode` is `true`; `outputPort` and `handoff` are live objects, and `intakeIsMember` is `true`. The setters return `void`; query the model when you need the objects they created.

These setters reconcile whole lists, not individual patches: an omitted node or link is removed. Removing a group through `setGroups()` keeps its boxes. For user-facing edits that belong on the undo stack, use [commands and history](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/commands-and-history) rather than treating reconciliation as an editing command.

## Geometry is intent, not a drawing instruction

Omit `ports` to get four deterministic bidirectional ports: top, right, bottom and left. Their ids use `<nodeId>__<side>`. Supply a port list when you need named endpoints, as the sample does. A handle pins an edge to a port; a bare side such as `'right'` also resolves to a port on that side.

In the sample, named handles resolve the edge spec to live ports, and the returned `handoff` exposes the resulting live link; see [How Grafloria works](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/how-grafloria-works) for endpoint and geometry intent. See [route and label edges](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/route-and-label-edges) and the [live edge demos](https://grafloria.com/demos/#edges).

A group is membership, not merely a rectangle behind nodes. Its `children` become members. `bounds` pins the frame; without it, reconciliation fits the frame around the children using `padding` (default `20`). Members travel with their group. See [group and nest nodes](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/group-and-nest-nodes) and the [live group demos](https://grafloria.com/demos/#grouping).

## Specs are not the persistence format

The instance also accepts live nodes and links: [`NodeInput`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-renderer-instance) is `NodeSpec | NodeModel`, and [`EdgeInput`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-renderer-instance) is `EdgeSpec | LinkModel`. The repository's Mermaid viewer passes parsed nodes, links and groups directly to the renderer instead of reducing them to a smaller spec projection.

Save the live model in the shared, versioned document format, not a framework's projection. Read [documents and kits](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/documents-and-kits) for that boundary, and [state and event flow](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/state-and-event-flow) for returning edits to controlled application state.
