Skip to content

Commit 81daf7f

Browse files
committed
Fix: Convergence and delete issue
- Fixed non-deterministic ordering in concurrent operations by forcing RGA ordering when inserting at the root position, ensuring all users see identical document states during real-time collaboration - Corrected array indexing in delete tests from sequence[1] to sequence[0] because getOrderedSequence() excludes the root node, making the first character at index 0 instead of 1
1 parent fbb5a3f commit 81daf7f

File tree

1 file changed

+22
-11
lines changed

1 file changed

+22
-11
lines changed

client/src/components/crdt/peritext-document.js

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ class PeritextDocument {
640640
}
641641

642642
// For remote operations: check if this appears to be part of a sequential typing session
643-
// Look at the immediate right neighbor to see if there's a pattern
643+
// Only treat as sequential if it's clearly part of the same user's typing sequence
644644

645645
// If the leftNode is from the same user and this is the immediate next counter,
646646
// treat as sequential
@@ -649,23 +649,34 @@ class PeritextDocument {
649649
return true;
650650
}
651651

652-
// If no immediate right neighbor or right neighbor is from different user/time,
653-
// treat as sequential (preserving user intent)
652+
// Special case: if leftNode is the root, always use RGA ordering for conflicts
653+
// This ensures concurrent inserts at the beginning are deterministically ordered
654+
if (leftNode.opId === this.root.opId) {
655+
return false; // Force RGA ordering for concurrent operations at root
656+
}
657+
658+
// Check if there's a right neighbor that conflicts
654659
if (!leftNode.rightId) {
655-
return true; // Appending to end
660+
// No right neighbor - this is truly appending to the end
661+
// Only treat as sequential if it's from the same user as leftNode
662+
return leftNode.userId === newNode.userId;
656663
}
657664

658665
const rightNode = this.characters.get(leftNode.rightId);
659-
if (!rightNode) return true;
666+
if (!rightNode) {
667+
// Missing right node data, treat as sequential to preserve intent
668+
return leftNode.userId === newNode.userId;
669+
}
660670

661-
// If right node is from different user or much later timestamp, treat as sequential
662-
if (rightNode.userId !== newNode.userId ||
663-
Math.abs(rightNode.timestamp - newNode.timestamp) > 1000) { // 1 second threshold
664-
return true;
671+
// If right node is from different user and timestamps are close (concurrent),
672+
// use RGA ordering to ensure deterministic resolution
673+
if (rightNode.userId !== newNode.userId &&
674+
Math.abs(rightNode.timestamp - newNode.timestamp) <= 1000) { // 1 second threshold
675+
return false; // Use RGA ordering for concurrent operations
665676
}
666677

667-
// Otherwise, use RGA ordering for concurrent operations
668-
return false;
678+
// If right node is from same user or much later timestamp, treat as sequential
679+
return true;
669680
}
670681

671682
/**

0 commit comments

Comments
 (0)