# Connect, Reconnect, And Disconnect

Connections can come from browser gestures or server code. Both paths submit
graph commands to the same canvas runtime.

Use browser attrs for interactive graph editing. Use `LivePanels.Graph` when
the application creates, removes, or repairs connectors from server code.

## Browser Connection Gestures

Start a connection on an output port:

```heex
<button
  {graph_port_anchor_attrs(port)}
  {begin_connector_attrs(context.item, port)}
>
  {port.label}
</button>
```

Complete it on an input port:

```heex
<button
  {graph_port_anchor_attrs(port)}
  {complete_connector_attrs(context.item, port)}
>
  {port.label}
</button>
```

Cancel a draft from any application control:

```heex
<button {cancel_connector_attrs()}>Cancel</button>
```

The browser owns the pending gesture. The server owns the accepted connector.

## Server Connections

Create a connector from server code:

```elixir
socket =
  LivePanels.Graph.connect!(socket,
    from: {"source-1", "out"},
    to: {"transform-1", "in"}
  )
```

Disconnect:

```elixir
socket = LivePanels.Graph.disconnect!(socket, connector_id)
```

Reconnect:

```elixir
socket =
  LivePanels.Graph.reconnect!(socket, connector_id,
    endpoint: :to,
    target: {"load-1", "in"}
  )
```

Use these helpers in application events, imports, domain synchronization, repair jobs,
and tests.

Use the bare helper when the caller needs the accepted connector or rejection
reason immediately:

```elixir
case LivePanels.Graph.connect(socket,
       from: {"source-1", "out"},
       to: {"transform-1", "in"}
     ) do
  {:ok, socket, _meta} ->
    {:noreply, socket}

  {:error, reason, socket, _meta} ->
    {:noreply, assign(socket, :connector_error, reason)}
end
```

## Validation Rules

Connection commands can be rejected when:

- the source or target item does not exist;
- either endpoint does not name a declared port;
- directions are invalid;
- port types are incompatible;
- `max_connections` would be exceeded;
- the actor cannot mutate the canvas;
- application policy rejects the command.

Rejected commands do not create partial connectors. Server code should use
`connect/2`, `disconnect/2`, or `reconnect/3` when the
application needs to show a reason. Browser gestures record their result on
`LivePanels.Canvas.Read.last_command/1`.

## Reconnect UX

Render reconnect controls on connector endpoints:

```heex
<button {begin_reconnect_attrs(connector, :to)}>
  Reconnect target
</button>
```

Then allow compatible input ports to complete the reconnect:

```heex
<button
  {graph_port_anchor_attrs(port)}
  {complete_reconnect_attrs(item, port)}
>
  {port.label}
</button>
```

Cancel reconnect state:

```heex
<button {cancel_reconnect_attrs()}>Cancel reconnect</button>
```

A reconnect draft is actor-local until completed. The accepted reconnect
updates the existing connector through the graph command path.

## Disconnect Controls

Render a connector removal button in an inspector:

```heex
<button {disconnect_attrs(connector)}>Remove connector</button>
```

Or make the connector hit target remove on a modified interaction through application
events. Keep the final mutation on `LivePanels.Graph.disconnect/2` or
`disconnect_attrs/2`.

## Connect From Domain Actions

If your product has a domain action such as "add dependency", do both pieces
explicitly:

```elixir
def handle_event("add-dependency", %{"from" => from_id, "to" => to_id}, socket) do
  :ok = MyApp.Dependencies.create(from_id, to_id)

  socket =
    LivePanels.Graph.connect!(socket,
      from: {from_id, "out"},
      to: {to_id, "in"}
    )

  {:noreply, socket}
end
```

If the canvas connector is purely a view of domain state, you can rebuild it from
domain data during mount or import. If users edit it in the canvas, decide how
and when that edit writes back to domain records.

## Policy-Gated Connections

Use `LivePanels.Canvas.Policy` for product rules that are not expressible as
port capabilities:

```elixir
def authorize(%LivePanels.Canvas.Policy.Request{action: :add_connector_item} = request) do
  if MyApp.GraphRules.allowed?(request.actor, request.details) do
    :ok
  else
    {:error, :connection_not_allowed}
  end
end
```

Use port direction, type, and max connection limits for runtime graph rules.
Use policy for app-specific authorization and workflow constraints.

## Transactions

Use a canvas transaction when a graph operation changes several things:

```elixir
socket =
  LivePanels.Canvas.transaction!(socket, fn tx ->
    tx
    |> LivePanels.Canvas.Item.add!(:step, id: "step-3", x: 420, y: 120)
    |> LivePanels.Graph.connect!(from: {"step-2", "out"}, to: {"step-3", "in"})
    |> LivePanels.Canvas.Selection.select!("step-3")
  end)
```

This creates one logical history entry and keeps the operation on the command
path.

## Shared Mode

In shared mode, pending connection drafts are local to the actor. Completed
connections are shared accepted state.

This keeps gestures responsive without broadcasting every pointer move, while
still making the final connector visible to other participants.
