Text suggestion system

I’m wondering if there is prior work around a suggestion system for a text document, similar to what Google Docs offers.
I’m thinking about the following:

  • When a user wants to suggest a change, the (root) document is “forked”, meaning that it is cloned and continuously updated with the updates from the root document (but not the other way around: the root document is not affected by the forked document yet).
  • The user can then make changes to the forked document.
  • The forked document can be merged back to the root document when the suggestion is accepted.

Since there can be several simultaneous suggestions, it doesn’t seem easy to make sense of all the forked documents, if we want to have them all represented in a unified view.
Does CodeMirror offer facilities around that? Are there other frontends that would be more appropriate?

I haven’t done anything like this, but I love the idea.

The upstream/fork model seems to map nicely to two Docs that share history:

Demo: View in CodeSandbox

// create a Y.Text
const docUpstream = new Y.Doc()
const ytextUpstream = docUpstream.getText()
ytextUpstream.insert(0, 'a') // insert 'abc'
console.log('upstream', ytextUpstream.toJSON())
const stateVector = Y.encodeStateVector(docUpstream)

// fork the Y.Text
const docFork = new Y.Doc()
Y.applyUpdate(docFork, Y.encodeStateAsUpdate(docUpstream))
const ytextFork = docFork.getText()

// edit fork
ytextFork.insert(2, 'x')
console.log('fork', ytextFork.toString())

// edit upstream
ytextUpstream.insert(2, 'd')
console.log('edit upstream', ytextUpstream.toString())

// pull upstream into fork
const updateFromUpstream = Y.encodeStateAsUpdate(docUpstream, stateVector)
Y.applyUpdate(docFork, updateFromUpstream)
console.log('pull upstream into fork', ytextFork.toString())

// merge fork
const updateFromFork = Y.encodeStateAsUpdate(docFork, stateVector)
Y.applyUpdate(docUpstream, updateFromFork)
console.log('merge fork into upstream', ytextUpstream.toString())

(One thing I didn’t do here was store the incremental updates. Cloning the whole Doc is fine in-memory, but I would probably probably only persist incremental updates on the fork to save space.)

I’m not aware of a pre-built front-end that can support the suggestions UI. That sounds like a custom job. It may be possible to apply attributes to demarcate suggested edits.

I see we are thinking along the same line, I also think there is no off-the-shelf UI solution.

Is this solution limited if 2 documents don’t share the same YJS history? We are working on advanced version control and use YJS, but are thinking of pursuing a JSON diffing strategy (our document is a JSON structure under the hood). That way, we can compare any 2 JSON documents and produce a diff of track changes. Might be important for us since we’re a legal app and many edit in MS Word off our platform. Thanks!

1 Like

In my mind, forking a document implies that they share a common history, and continuously updating the fork with the updates from the root document also implies that.
Otherwise, if it’s only about diffing two JSON values, then it seems that CRDTs are not even needed?

It may be possible to apply attributes to demarcate suggested edits.

But attributes only apply to Y.Text, right? So it wouldn’t work if I want to show updates to other data structures, like Y.Array.
I’m thinking that it would be better to use origins and display fork updates differently depending on their origin.

Indeed the 3rd argument could be used Y.applyUpdate(Y.Doc, update:Uint8Array, [transactionOrigin:any]).