Learn / Collaboration

Collaboration

Two people drag the same diagram and both keep their changes. The CRDT lives in the engine — you bring a message channel, Grafloria brings convergence, presence and comments.

The one-prop version

// React
<GrafloriaFlow defaultNodes={nodes} defaultEdges={edges}
  collab={{ transport, actor: 'lena' }}
  onCollabReady={(session) => console.log('joined')} />

// Angular
<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges"
  [collab]="{ transport, actor: 'lena' }" (collabReady)="onReady($event)" />

// Vue
<GrafloriaFlow :default-nodes="nodes" :default-edges="edges"
  :collab="{ transport, actor: 'lena' }" @collab-ready="onReady" />

The canvas joins a sync session at mount and leaves at unmount. Every local edit becomes a CRDT operation broadcast to peers; every inbound operation merges — per property, so one user recoloring a node while another moves it produces both, not a conflict dialog.

Bring any transport

A transport is a tiny interface — send(message), onMessage(handler), onStatus(handler), connect()/disconnect(). Anything that moves JSON qualifies: WebSocket, WebRTC, or — the demo favorite, because it needs no server at all — a BroadcastChannel between two tabs of the same browser.

Reconnects are handled for you — the session listens for the transport's 'connected' status and runs an anti-entropy round the moment the channel comes back, so peers that were offline catch up automatically. A transport that never reports status can never be caught up: implement onStatus.

Live: open twice, drag in both tabs →

Presence and comments ride along

The same session carries live cursors (each peer sees where others point and what they select) and anchored comment threadscomments: true on any canvas creates a store; pass a shared CommentStore to sync threads across peers, and drop in the ready-made panel (<GrafloriaCommentPanel> in all three frameworks).

The engine level — headless sync

The frameworks call the same public seam you can: a session binds a diagram to a transport, and it runs anywhere the engine runs — including Node, which is how you build a persistence peer (a headless replica that joins the room and writes every converged state to a database):

import { createSyncSession } from '@grafloria/engine';

const session = createSyncSession(diagram, transport, { actor: 'server-writer' });
// same CRDT, no renderer — a Node process is just another peer

What it is not

Honesty corner: Grafloria gives you convergence, presence, comments and an op-log — it does not give you rooms, auth, or storage. Those stay in your product, attached through the transport and the serialized document. That is a deliberate seam, not a gap: every team's room model is different.

Where next