The question is a bit hard to phrase, so I think it is easier just to show:
import * as Y from 'yjs'
const y1 = new Y.Doc()
const y1text = y1.getText('text')
y1text.insert(0, '[ ] my todo')
const upStart = Y.encodeStateAsUpdate(y1)
const y2 = new Y.Doc()
Y.applyUpdate(y2, upStart)
y1.transact(() => {
y1text.delete(1, 1)
y1text.insert(1, 'x')
})
const y2text = y2.getText('text')
y2.transact(() => {
y2text.delete(1, 1)
y2text.insert(1, 'x')
})
const up1 = Y.encodeStateAsUpdate(y1)
const up2 = Y.encodeStateAsUpdate(y2)
const y3 = new Y.Doc()
Y.applyUpdate(y3, upStart)
Y.applyUpdate(y3, up1)
Y.applyUpdate(y3, up2)
console.log(y3.getText('text').toString()) // outputs: '[xx] my todo'
// I want it to output '[x] my todo' after both updates are applied
Basically, I have certain instances (in this case “checking” a todo list in text) where I know I always want to perform what I will call a “strong” deletion with a Y.Text
. If multiple users make the same deletion, the logic employed by YJs’ CRDTs is to merge those deletions and then perform both insertions (excuse my layman terminology).
In this particular case, I do not want the deletions to be “merged”. I want whichever operations (update) come first to initially remove a space (' '
) and then insert an 'x'
. I then want whichever operations (update) are applied second to remove the original 'x'
and replace it with their own 'x'
.
The default YJs behavior is for that second update to “see” that the ' '
has already been removed, do nothing for the delete()
step, and then insert a second 'x'
, which is not the desired behavior in this particular case.
Said in terms of user experience: if two users “check” the checkbox asynchronously, I want the final result to be "[x] my todo"
. Likewise if two users uncheck I want the result to be "[ ] my todo"
rather than "[__] my todo"
(with two spaces).
Is there any way to force YJs to do it like this for these particular operations? I understand that most of the time the standard behavior is what you want.