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:
<button
{graph_port_anchor_attrs(port)}
{begin_connector_attrs(context.item, port)}
>
{port.label}
</button>Complete it on an input port:
<button
{graph_port_anchor_attrs(port)}
{complete_connector_attrs(context.item, port)}
>
{port.label}
</button>Cancel a draft from any application control:
<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:
socket =
LivePanels.Graph.connect!(socket,
from: {"source-1", "out"},
to: {"transform-1", "in"}
)Disconnect:
socket = LivePanels.Graph.disconnect!(socket, connector_id)Reconnect:
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:
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)}
endValidation 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_connectionswould 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:
<button {begin_reconnect_attrs(connector, :to)}>
Reconnect target
</button>Then allow compatible input ports to complete the reconnect:
<button
{graph_port_anchor_attrs(port)}
{complete_reconnect_attrs(item, port)}
>
{port.label}
</button>Cancel reconnect state:
<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:
<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:
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}
endIf 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:
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
endUse 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:
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.