We’ve been working on a plugin to bind Automerge and Prosemirror together and I wanted to check my approach was sane.
My strategy is to let Automerge handle what it’s good at: interleaving edits from a variety of sources, and let Prosemirror handle what it’s good at: being an extensible rich text editor. Thus, we write all Prosemirror transactions into Automerge immediately, and let Automerge produce patches which are converted into Prosemirror transactions.
I have experimented with a few different approaches: one is to let Prosemirror handle all local updates and simply mirror them to Automerge, but my concern is that this may be fragile in edge cases.
Perhaps the ideal race-condition-avoiding strategy would be to have a “closed loop” where all durable edits out of Prosemirror are run through the local Automerge object but it seems likely that there will be local Prosemirror state that Automerge should neither synchronize nor store.
In this case, I think I’d want to intervene in the transaction, apply the change to Automerge, collect its reply from how to update the patch and then combine that with the “rest” of the transaction but while I think this would improve fidelity of text editing it might introduce new problems with how other plugins behave.
A couple of the things we’ve tried and why they haven’t worked out:
- writing changes into Automerge in the apply handler
- This works provided the patch Automerge generates later is sufficiently (?) similar, in which case Prosemirror appears to “de-dupe” the change. It fails if the patch is too different and I worry the result is fragile. This may be salvageable if we just discard local patches.
- intercepting prosemirror-originating transactions using filterTransaction and producing new “unrelated” transactions from the resulting Automerge changes.
- This “works”, except it consumes other non-step edits such as cursor positions. I have a few ideas about how to propagate that information but I can only filterTransaction or appendTransaction. What I’d really like to do (I think?) is replace the transaction.
I should say that we have had some success with a variety of different approaches, including in the Peritext paper from last year and the recent Upwelling work as well, but both those approaches felt a little fragile and I’d like to get us on more principled footing.
Alright, I’ll leave it at that and hope for some feedback. I’m open to suggestions about either how to improve on our approach or entirely different approaches we should consider.