CRDT for text editors
@ian what was the reason to choose automerge vs Yjs?
I currently use Yjs to sync a react flow nested tree structure, where each node has a lot of data including many text editor blocks (currently using blocknote based on tiptap+prosemirror).
But the Yjs sync with the react state manager became too complex and slow, and wanted to switch to convex for everything except the text editors to handle merges / conflicts better. And blocknote is not good for nested schemas (I want nested custom block types, some react RSC) so I need to switch to pure prosemirror or another CRDT text editor.
Do you recommend switching to automerge, or is Yjs equaly good? Prosemirror OT with pure convex may be too slow without a proper CRDT.
I still need to use a production Yjs or automerge server like partykit even if I use convex as the persistence storage right?
@RJ and @ampp can you also chime in since you worked on similar things?
Thanks!
20 Replies
Thanks for posting in <#1088161997662724167>.
Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets.
- Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.)
- Use search.convex.dev to search Docs, Stack, and Discord all at once.
- Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI.
- Avoid tagging staff unless specifically instructed.
Thank you!
can you saw more about why OT might be slow?
especially b/c most large scale collab editors are OT, not CRDTs
just what I read somewhere, was not aware of that. but if that is the case, why do you have the automerge article on your website, instead of using convex as the storage for prosemirror OT or something similar? is there a reference implementation / project?
aparently OT implementation is a lot more difficult, and harder to work offline, so most OSS systems use CRDTs
another reason is that tiptap and blocknote already do a lot of the work to create an extensible collaborative block document editor but use Yjs extensively, and reimplementing in pure prosemirror OT could be difficult, but I'm open to alternatives that work better with convex
we never could get blocknote to work on our mono-repo
we were going to mess with lexical but that got delayed
@Tiago Freitas I currently have a repo where I have prosemirror OT syncing with Convex. A live demo is at https://prosemirror-convex-sync.vercel.app that you can play around with. It's currently using TipTap (the layer between BlockNote and ProseMirror), which might be flexible enough for your purposes? I gave a talk last night on local first things: https://x.com/i/broadcasts/1MYxNMdLpgoJw
I'm still cleaning up the prosemirror implementation, but the goal is that it's a component that syncs the ProseMirror contents via Convex. The goal is to have a Convex Component paired with a React hook so you can sync the document, have entrypoints to authorize reads & writes, access data snapshots, and have some limited offline persistence - limited to the browser tab (session storage). So you could be offline for a plane flight, edit, refresh the page, even quit the browser if it restores tabs. Then when you're online and it loads, it'll sync up the changes.
The current prototype doesn't yet have: snapshotting, offline implementation, ergonomic validation/authorization entrypoints. All have been thought out, just haven't done it. Maybe this week.
It's currently working directly with ProseMirror OT (steps), and is packaged as a TipTap Extension. I'm considering a few things that may not come to fruition, so interested in what folks are interested in:
1. Make it a ProseMirror plugin, so you could sync raw ProseMirror projects, TipTap, or BlockNote
2. Make a YJS syncing component, and use that in its own right, as well as to power ProseMirror / TipTap / BlockNote. This could then leverage the existing yjs extensions for offline support, and maybe act as an official BlockNote / TipTap collaboration provider, rather than at the lower level.
This is perfect timing
So it already syncs tiptap and blocknote right? A prosemirror plug-in would only add the ability of using prosemirror without tiptap. Isn't that what scroll by @RJ already does?
As for 2., they are already powered by yjs, so what would be the difference here? Sync yjs with convex? What is the advantage compared to just storing the Yjs doc in convex or another persistence method?
If it's performant enough without yjs for most use cases then you just need to add offline to convex itself and no need for Yjs
Also can you comment on automerge vs yjs in case a crdt is needed
I need to ditch blocknote because the schema is too limited for nested blocks with rules on the block types allowed. but will take some development effort, but I guess tiptap would still be the best way to do it, or is there a better alternative
Exactly yeah, that's how Scroll works, but it sounds like Ian is going to package it up in a Convex component for easy project integration, and add more features.
Which is awesome
Also, I agree with Tiago that a ProseMirror plugin would be ideal. It's trivial (I think) to convert them to Tiptap plugins: https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#prosemirror-plugins-advanced
Yeah I would explore TipTap, since it's the same model but just a layer deeper with more flexibility. Hopefully it doesn't require dropping down to raw ProseMirror but that's an option too. You probably have more hours of experience with these than I do, so take my advice here with a grain of salt.
Yeah as RJ mentioned a prosemirror plugin would allow syncing ProseMirror, TipTap and BlockNote. However, to add things like presence & seeing each others text cursors/ selections I think it'd be easier to integrate directly with TipTap or BlockNote, so the current plugin would be limited to syncing the contents.
My implementation is similar to scroll & I looked through that code for inspiration. It has some optimizations though: it doesn't fetch the full history to calculate the current server version, it doesn't re-generate the content on every delta, it avoids syncing down changes you yourself made, and cuts out a round trip when you try to apply changes when there's changes you haven't fetched yet. Some of these could get applied to scroll, but I'm hoping to just abstract it into a component to make it easy to work with.
The difference here would be that instead of syncing around OT "steps", it syncs around the yjs deltas, along with occasional full yjs doc snapshots. This allows doing things like syncing deltas between browser tabs, (or maybe even optimistic peer to peer). Instead of just sending the whole yjs doc on each change, it would be calculating the deltas to upload / download, so it's a bit more nuanced than just storing the yjs doc in an arbitrary persistance layer
Is there a link to a github repo or is it still private?
Yeah, it's still a WIP but it's public: https://github.com/get-convex/prosemirror-sync
GitHub
GitHub - get-convex/prosemirror-sync: Sync prosemirror documents wi...
Sync prosemirror documents with Convex for server-authorized collaborative editing - get-convex/prosemirror-sync
I need to update the README at the very least 😅
I need to a tool to sleuth all the repos so i can stay up to date 😅
Great point, and also this sounds fantastic
so that solution would do away with a yjs websocket server and be peer to peer only, with convex a persistence but using deltas right. and OT is not performant enough to do browser tab sync? but google docs is fast enough for that. because if OT is good enough for most use cases, Yjs just adds a lot of complexity. or is it that tiptap collaboration features already use Yjs directly and so a prosemirror OT does not sync everything?
and I guess a custom presence implementation using convex for tiptap is more complex than implementing the Yjs delta sync with convex? makes sense to support more of upstream code, just worry about complexity, OT only was sounding good if it worked
OT is just not a great fit for browser tab sync since it applies changes in a linear way, and keeping track of all the rebasing of changes from other tabs and deciding who syncs what is less trivial than with CRDT which is more loosey-goosey (more of a git merge than git rebase) - check out my talk https://x.com/i/broadcasts/1MYxNMdLpgoJw
Yeah i better listen to that before asking too many questions about the new Local Sync and peer to peer possibilities 🙂
Yeah my thoughts on making a yjs component are that there's a bunch of existing extensions & work with Yjs already that I could leverage if I just implement Yjs sync
Yeah I'm going to push this one through to do OT-only and then look at Yjs. I agree that avoiding that complexity sounds good, but if syncing Yjs is a transparent layer, then maybe it won't be much for end users to worry about. they'll just set up the extension, let my component do the syncing, and just have a similar API to read document snapshots, make changes, have authorization logic on the server, etc. TipTap and BlockNote use Yjs directly for their collab features, so yeah it'd be something more custom to build with Convex (and I really don't want to add & support custom UI presence elements if I don't have to, would like to just support a small well defined syncing layer)
this sounds awesome and is just what I needed. Yjs has been a nightmare to sync with react and keep performant (as I'm using it for other data not just blocknote), and replacing it with convex for my whole app in one go sounds perfect. then I won't need a Yjs websocket server anymore right, convex will handle that replacing livekit?
so when/if you implement Yjs sync, you don't need to use OT anymore for Tiptap? or a combination of OT and Yjs depending on the situation?
Yeah Yjs would replace the OT implementation. So you'd use Convex to sync YJS, which could replace the OT approach. It may come with some caveats which come up when building with it. The OT approach would then be the simpler approach for folks who don't want to go with yjs / prefer the OT model