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.