Graph apps need topology to survive reloads and collaboration.
LivePanels keeps graph connectors inside canvas state so layouts, history, clipboard, and shared mode can handle graph behavior without a second graph store.
Persist Graph Layouts
Configure a layout store:
use LivePanels.Canvas,
pubsub: MyApp.PubSub,
session: [layout_store: MyApp.GraphLayouts]
use LivePanels.Toolkit.GraphThe versioned layout payload can include:
- node item ids, types, placement, visibility, and z-order;
- persistent node data;
- graph connectors between persistent items;
- connector presentation that belongs to canvas state;
- vector state if the graph app also uses vector items.
Persist the layout payload unchanged.
Restore Requires Item Types And Ports
When restoring a graph layout, the canvas must know the item types and ports referenced by the saved graph.
Before shipping persistence, test:
- every saved node type still exists;
- every saved connector endpoint still names a declared port;
- saved connector endpoints still match declared port names;
max_connectionsrules still allow restored topology;- hidden or removed item types do not break room recovery.
If your app changes port names between releases, transform app-owned layout data before restoring connectors.
Clipboard
Graph clipboard copies selected items and internal connector items:
<button {copy_selection_attrs()}>Copy</button>
<button {paste_selection_attrs()}>Paste</button>If selected nodes have connectors to unselected nodes, only internal connector items are copied with the selected group. This prevents pasted graph fragments from pointing at the original graph by accident.
History
Graph commands participate in canvas history:
<button {undo_attrs()}>Undo</button>
<button {redo_attrs()}>Redo</button>Use transactions when one user action should create one history entry:
socket =
LivePanels.Canvas.transaction!(socket, fn tx ->
tx
|> LivePanels.Canvas.Item.add!(:step, id: "step-4")
|> LivePanels.Graph.connect!(from: {"step-3", "out"}, to: {"step-4", "in"})
end)Shared Graph Canvases
Enable shared mode normally:
use LivePanels.Canvas,
pubsub: MyApp.PubSub,
session: [
mode: :shared,
id: {:param, "workflow_id"},
backend:
{LivePanels.Canvas.Shared.Local,
layout_store: MyApp.GraphLayouts}
]
use LivePanels.Toolkit.GraphAccepted graph changes are shared:
- node add/remove/place;
- connector connect/reconnect/disconnect;
- connector bend updates;
- graph layout commands;
- selection commands when the selected state is shared for that actor.
Connection drafts remain actor-local until completed.
Participant Viewports
Shared graph state does not mean everyone has the same camera.
Viewport fit is actor-scoped:
socket = LivePanels.Graph.fit(socket, :selection)Use this for "follow selected", "fit my view", or "jump to node" interactions. If the product needs a presenter mode, model that separately in your app or participant metadata.
Domain Synchronization
Decide whether canvas connectors are:
- the source of truth for the graph topology;
- a view of domain records;
- an editable projection that writes back to domain records.
For source-of-truth canvas graphs, persistence may be enough.
For domain-backed graphs, store domain records in your app and rebuild or update canvas connectors from those records. When users edit the canvas, write the domain record and then submit the LivePanels command for the canvas effect.
Recovery Checklist
Before production:
- run
mix livepanels.check; - run
LivePanels.Canvas.Shared.backend_health_check/1for shared graph backends; - verify layout restore with real saved graph payloads;
- test port rename and item type removal scenarios;
- test copy/paste of graph fragments;
- test undo/redo for connect, reconnect, disconnect, layout, and node removal;
- monitor layout persistence failures.
Graph recovery bugs usually come from unstable item ids, unstable port names, or domain state that was not restored before the canvas layout.