# Model Nodes, Ports, And Connectors

Graph apps in LivePanels start with canvas items.

A node is an item. A port is an item capability. A connector is a canvas item
that connects endpoints. That model is what lets graph apps reuse item
placement, selection, z-order, history, clipboard, persistence, and shared mode.

## Nodes Are Items

Declare graph nodes as items with port capabilities:

```heex
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
  <:item
    id="source-1"
    type="source"
    at={{120, 80}}
    size={{520, 360}}
    ports={[
      %{name: "out", direction: :output, type: :data}
    ]}
  >
    <.source_node />
  </:item>

  <:item
    id="transform-1"
    type="transform"
    at={{720, 80}}
    size={{560, 400}}
    ports={[
      %{name: "in", direction: :input, type: :data, max_connections: 1},
      %{name: "out", direction: :output, type: :data}
    ]}
  >
    <.transform_node />
  </:item>
</.canvas_runtime>
```

The node can render LiveView content, static content, or app-only data. Graph
behavior comes from the declared ports, not from a separate node system.

## Ports Are Declared Capabilities

A port declaration names the endpoint and the rules LivePanels can validate:

```elixir
%{
  name: "out",
  label: "Rows",
  direction: :output,
  type: :data,
  max_connections: :unlimited,
  anchor: %{edge: :right, offset: 0.5, inset: 0}
}
```

Common fields:

| Field | Purpose |
|---|---|
| `:name` | Stable endpoint name used by commands and connectors. |
| `:label` | Optional app-facing label. |
| `:direction` | `:input` or `:output`. |
| `:type` | Compatibility group such as `:data`, `:image`, or `:task`. |
| `:max_connections` | Positive integer or `:unlimited`; omitted means unlimited. |
| `:anchor` | Geometry used to resolve the port position against the item frame. |

Use stable port names. If a saved layout contains a connector to `"out"`, the
item type should still declare `"out"` when the layout restores.

## Port Geometry

Port anchors are resolved from the item frame.

Edge anchors:

```elixir
%{edge: :right, offset: 0.5, inset: 0}
```

Explicit coordinates:

```elixir
%{x: {:px_from_end, 24}, y: 32}
```

For row-based nodes, compute offsets from row height:

```elixir
defp column_ports do
  for slot <- 0..8, side <- [:input, :output] do
    %{
      name: port_name(slot, side),
      label: "Column #{slot}",
      direction: side,
      type: :field,
      anchor: %{
        edge: if(side == :input, do: :left, else: :right),
        offset: 56 + slot * 32 + 16,
        inset: 0
      }
    }
  end
end
```

The browser does not measure DOM handles and report them back as canonical
state. The server can resolve connector endpoints from item placement and
declared anchors.

## Connectors Are Canvas Items

A connector is a `%LivePanels.Item{type: :connector}`. Use
`LivePanels.Item.Connector` to build and inspect connector items.

Connection connectors point from one item port to another:

```elixir
LivePanels.Item.Connector.connection(
  from: {"source-1", "out"},
  to: {"transform-1", "in"},
  label: "rows",
  role: :data
)
```

Relationship connectors connect item centers:

```elixir
LivePanels.Item.Connector.relationship("idea-1", "idea-2",
  label: "related"
)
```

Use connection connectors when endpoint ports matter. Use relationship
connectors when the product only needs item-to-item relationships.

## Starting Connectors

Canvas applications can start with connector topology:

```heex
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
  <:item
    :for={node <- @nodes}
    id={node.id}
    type="workflow_node"
    at={{node.x, node.y}}
    size={{node.w, node.h}}
    ports={node.ports}
  >
    <.workflow_node node={node} />
  </:item>

  <:item :let={context}>
    <.workflow_node item={context.item} />
  </:item>
</.canvas_runtime>
```

Create starting connectors with `LivePanels.Graph.connect/2` after the endpoint
items exist in canvas state, or restore them from layout persistence. If an
endpoint cannot be resolved, the connector command is rejected instead of
creating a partial graph.

## Canvas Data Versus Domain Data

Keep this distinction clear:

- LivePanels connector state says item A's port connects to item B's port.
- Your app decides whether that means a workflow transition, foreign key,
  dataflow pipe, dependency, or relation.

If a connection should create a durable domain record, handle that in your application
or app layer and submit the LivePanels graph command for the canvas effect.

## Design Checklist

Before rendering the graph, decide:

- what item types are graph nodes;
- which ports are stable and restorable;
- which port types are compatible;
- whether port connection limits are runtime rules or product policy;
- whether connectors are connection connectors or relationship connectors;
- which connector presentation fields your application will use for labels,
  roles, markers, routes, and bends;
- which domain records, if any, mirror canvas connectors.
