fix(collaboration): seed CRDT graph before leader resync

Avoid writing partial graph into CRDT during initial HTTP draft parsing in workflow-app.

Seed CRDT from current ReactFlow graph only when CRDT is empty and collaboration is connected, then reuse it for leader snapshot rebroadcast.

This prevents join-time resync from broadcasting an empty snapshot when leader has not produced local CRDT mutations yet.
This commit is contained in:
hjlarry 2026-04-12 15:41:54 +08:00
parent a29e831ce5
commit 828276d672
2 changed files with 31 additions and 5 deletions

View File

@ -10,7 +10,6 @@ import { useStore as useAppStore } from '@/app/components/app/store'
import { FeaturesProvider } from '@/app/components/base/features'
import Loading from '@/app/components/base/loading'
import WorkflowWithDefaultContext from '@/app/components/workflow'
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
import {
WorkflowContextProvider,
} from '@/app/components/workflow/context'
@ -81,7 +80,6 @@ const WorkflowAppWithAdditionalContext = () => {
const nodesData = useMemo(() => {
if (data) {
const processedNodes = initialNodes(data.graph.nodes, data.graph.edges)
collaborationManager.setNodes([], processedNodes)
return processedNodes
}
return []
@ -90,7 +88,6 @@ const WorkflowAppWithAdditionalContext = () => {
const edgesData = useMemo(() => {
if (data) {
const processedEdges = initialEdges(data.graph.edges, data.graph.nodes)
collaborationManager.setEdges([], processedEdges)
return processedEdges
}
return []

View File

@ -1538,10 +1538,13 @@ export class CollaborationManager {
const wasLeader = this.isLeader
this.isLeader = data.isLeader
if (this.isLeader)
if (this.isLeader) {
this.seedCrdtGraphFromReactFlowIfNeeded()
this.pendingInitialSync = false
else
}
else {
this.requestInitialSyncIfNeeded()
}
if (wasLeader !== this.isLeader)
this.eventEmitter.emit('leaderChange', this.isLeader)
@ -1603,6 +1606,30 @@ export class CollaborationManager {
})
}
private seedCrdtGraphFromReactFlowIfNeeded(): void {
if (!this.doc)
return
if (!this.reactFlowStore)
return
// CRDT may still be empty when the canvas was initially loaded from HTTP draft data
// before collaboration finished connecting, and no local mutation has been written yet.
// Seed once from the current ReactFlow graph so leader resync can broadcast a full snapshot.
if (this.getNodes().length > 0 || this.getEdges().length > 0)
return
const state = this.reactFlowStore.getState()
const nodes = state.getNodes()
const edges = state.getEdges()
if (!nodes.length && !edges.length)
return
this.syncNodes([], nodes)
this.syncEdges([], edges)
this.doc.commit()
}
private broadcastCurrentGraph(): void {
if (!this.currentAppId || !webSocketClient.isConnected(this.currentAppId))
return
@ -1614,6 +1641,8 @@ export class CollaborationManager {
return
try {
this.seedCrdtGraphFromReactFlowIfNeeded()
if (this.getNodes().length === 0 && this.getEdges().length === 0)
return