25 ms·
Lies I was told about collaborative editing, Part 2: Why we don't use Yjs
- presspot 7mo agoReplacing CRDT with 40 lines of code. Amazing.
- GermanJablo 7mo agoI remember reading Part 1 back in the day, and this is also an excellent article. I’ve spent 3+ years fighting the same problems while building DocNode and DocSync, two libraries that do exactly what you describe. DocSync is a client-server library that synchronizes documents of any type (Yjs, Loro, Automerge, DocNode) while guaranteeing that all clients apply operations in the same order. It’s a lot more than 40 lines because it handles many things beyond what’s described here. For example: It’s local-first, which means you have to handle race conditions. Multi-tab synchronization works via BroadcastChannel even offline, which is another source of race conditions that needs to be controlled. DocNode is an alternative to Yjs, but with all the simplicity that comes from assuming a central server. No tombstones, no metadata, no vector clock diffing, supports move operations, etc. I think you might find them interesting. Take a look at https://docukit.dev https://docukit.dev and let me know what you think.
- freekh 7mo agoCool! We also build client-server sync for our local-first CMS: https://github.com/valbuild/val https://github.com/valbuild/val Just as your docsync, it has to both guarantee order and sync to multiple types of servers (your own computer for local dev, cloud service in prod). Base format is rfc 6902 json patches. Read the spec sheet and it is very similar :)
- huksley 7mo agoLooks really cool, I would love to use it in my DollarDeploy project. Documentation could be a bit better still, it is not clear, are content is pure markdown or it is typescript files? Which GitHub repo it synchronizes to? I prefer monorepo approach.
- antics 7mo agoHello again Germán! Since the product we make is, basically, a local-first markdown file editor, I would humbly suggest that the less-well-known algorithm we recommend is thus also local-first. But, I fully believe that you do a ton of stuff that we don't, and if we had known about it at the time, we very definitely would have taken a close look! We did not set out to do this ourselves, it just kind of ended up that way.
- eviks 7mo agoTiny fail at undo: insert 1 before E, Ctlr+Z, move left/right: left editor moves around E, right editor moves around the nonexistent 1 And for real "action" there should be a delay/pause button to simulate conflicts like the ones described in the blog
- GermanJablo 7mo agoYes, the undo issue is a known bug in the website demo because it's messing with Lexical's undo functionality. It's not actually a DocNode bug. I'll fix it soon. The feedback about the delay/pause button is also good, thanks!
- gritzko 7mo agoAt least Yjs, Loro and Automerge must handle some degree of operation reordering. Either causally-consistent or virtually any order.
- tobr 7mo agoPart 1 discussion, December 2024: https://news.ycombinator.com/item?id=42343953 https://news.ycombinator.com/item?id=42343953
- samlinnfer 7mo agoJust use OT like normal people, it’s been proven to work. No tombstones, no infinite storage requirements or forced “compaction”, fairly easy to debug, algorithm is moderate to complex but there are reference open source implementations to cross check against. You need a server for OT but you’re always going to have a server anyway, one extra websocket won’t hurt you. We regularly have 30-50k websockets connected at a time. CRDTs are a meme and are not for serious applications.
- chrisweekly 7mo ago"CRDTs are a meme and are not for serious applications." That is one hot take!
- argee 7mo agoLet's balance the discussion a bit. https://josephg.com/blog/crdts-are-the-future/ https://josephg.com/blog/crdts-are-the-future/
- chrisweekly 7mo agoGreat link! Thanks for sharing.
- antics 7mo agoAuthor here, I did not specifically mention OT in the article, since our main focus was to help people understand the downsides of the currently-most-popular system, which is built on CRDTs. BUT, since you mention it, I'll say a bit here. It sounds like you have your own experience, and we'd love to hear about that. But OUR experience was: (1) we found (contrary to popular belief) that OT actually does not require a centralized server, (2) we found it to be harder to implement OT exactly right vs CRDTs, and (3) we found many (though not all) of the problems that CRDTs have, are also problems in practice for OT—although in fairness to OT, we think the problems CRDTs have in general are vastly worse to the end-user experience. If there's interest I'm happy to write a similar article entirely dedicated to OT. But, for (3), as intuition, we found a lot of the problems that both CRDTs and OT have seem to arise from a fundamental impedance mismatch between the in-memory representation of the state of a modern editor, and the representation that is actually synchronized. That is, when you apply an op (CRDT) or a transform (OT), you have to transform the change into a (to use ProseMirror as an example) valid `Transaction` on an `EditorState`. This is not always easy in either case, and to do it right you might have to think very hard about things like "how to preserve position mappings," and other parts of editor state that are crucial to (say) plugins that manage locations of comment marks or presence cursors. With all of that said, OT is definitely much closer to what modern editors need, in my opinion at least. The less-well-known algorithm we ended up recommending here (which I will call "Marjin Collab", after its author) is essentially a very lightweight OT, without the "transformation" step.
- bawolff 7mo agoReminds me a bit of google-mobwrite. I wonder why that fell out of favour.
- truetraveller 7mo agoVery likely AI slop, very hard to read. Too many indications. HN should have another rule: explicitly mention if article was written (primarily) by AI.
- utopiah 7mo agoFunnily enough I had 2 HN tabs open, this one and https://news.ycombinator.com/item?id=47394004 https://news.ycombinator.com/item?id=47394004
- antics 7mo agoI'm the author. Literally 0% of this was written with AI. Not an outline, not the arguments, not a single word in any paragraph. We agonized over every aspect of this article: the wording, the structure, and in particular, about whether we were being fair to Yjs. We moved the second and third section around constantly. About a dozen people reviewed it and gave feedback. EDIT: I will say I'm not against AI writing tools or anything like that. But, for better or worse, that's just not what happened here.
- truetraveller 7mo agoApologies. Was it at all edited by an AI?
- antics 7mo agoI do not know how to say this another way. No. Nothing in this article was created, edited, outlined, pre-drafted, suggested, or in any other way influenced by AI. AI did not write the words, it did not review the words, it was not a part of a pre-writing discussion that influenced the words. There is NO AI IN THIS ARTICLE OR AS AN INPUT TO THIS ARTICLE. If the article is an apple, then 0% of the apple is AI or AI influenced. And let this be a lesson to you. You apparently do not have any ability to distinguish between these two kinds of thing.
- comex 7mo agoIt doesn’t strike me as AI. The writing is reasonably information-dense and specific, logically coherent, a bit emotional. Rarely overconfident or vague. If it is AI then there was a lot more human effort put into refining it than most AI writing I’ve read.
- kaiwenwang 7mo agoIt appears Moment is producing "high-performance, collaborative, truly-offline-capable, fully-programmable document editor" - https://www.moment.dev/blog https://www.moment.dev/blog There seems to be a conflict of interest with describing Yjs's performance, which basically does the same thing along with Automerge.
- antics 7mo agoAuthor here. To be clear, we do not in ANY WAY compete with Yjs! We are a potential customer of Yjs. This article explains why we chose not to be a customer of Yjs, and why we don't think most people building real-time collaborative text editors should be, either.
- crashabr 7mo agoYou have an amazing tagline. This is the first time I read a tagline and thought: this is exactly what I was looking for. But the product seems much more narrow than an actual tool run the whole business in markdown. I was hoping to see Logseq on steroids, and it feels like a tool builder primarily. I love the tool building aspect, but the fundamentals of simply organizing docs (docs, presentations, assets etc, the basics of a business) are either not part of the core offering or not presented well at all. I love the idea of building custom tools on top of MD and it's part of my wishlist, but I feel little deceived by your tagline so I wanted to share that :)
- antics 7mo agoThis is great feedback, thank you. I will say that IS our goal... but we only really launched last week and are still figuring out what resonates with people and what they really want! It sounds like you're saying that the organization aspects are not there, which is very helpful to know... I am not quite sure I understand if you also think the toolbuilding is lacking? If you are open to it, I'd love the opportunity to hear more. Here or email (alex@moment.dev) or our Discord (bottom right of our website) or Twitter/X... or whatever you prefer.
- antics 7mo agoHi folks, author here. I thought this was dead! I'm here to answer questions if you have them. EDIT: I live in Seattle and it is 12:34, so I must go to bed soon. But I will wake up and respond to comments first thing in the morning!
- drpotato 7mo agoJust wanted to say thanks! This is a great write up and resonates with issues I encountered when trying to productionise a yjs backed feature.
- marijn 7mo agoHi Alex, I'm the author of prosemirror-collab. I agree with your point that CRDTs are not the solution they often claim to be, and that CRDT editor integrations (at least the ones for Yjs and Automerge) are often shockingly sloppy. But, seeing how I've had several people who read your article write me asking about this miraculous collab implementation, I want to push back on the framing that ProseMirror's algorithm is 'simple' or '40 lines of code'. The whole document and change model in ProseMirror was designed to make something like prosemirror-collab possible, so there is a lot of complexity there. And it has a bunch of caveats itself—the server needs to hold on to changes as long as there may be clients to need to catch up, and if you have a huge amount of concurrent edits, the quadratic complexity of the step mapping can become costly, for example. It was designed to support rich text, document schemas, and at least a little bit of keeping intentions in mind when merging (it handles the example in the first post of your series better, for example), but it's not a silver bullet, and I'd hate for people to read this and go from thinking 'CRDT will solve my problems' to 'oh I need to switch to ProseMirror to solve my problems'.
- antics 7mo agoOk Marijn I understand. I'm sorry I caused you an inconvenience. Of course, I know that implementing server-side prosemirror-collab is not entirely without problems (since we have done it) and take your point, which is correct. If I was to do this again I'd find a different way to say this than "40 lines of code." With that said... I do not agree it is not "simple." Or at least, I think it is about as simple as it can possibly be.
- skeptrune 7mo agowe're about to implement collaborative editing at Mintlify and were considering yjs so this couldn't have come at a better time
- antics 7mo agoAuthor here, my personal mission is for people implementing this to have clear, actionable advice. Which is something we did not when we started. If you want to chat about it I'm happy to help, just email me: clemmer.alexander@gmail.com
- samwillis 7mo agoIt's disingenuous to suggest that "Yjs will completely destroy and re-create the entire document on every single keystroke" and that this is "by design" of Yjs. This is a design limitation of the official y-Prosemirror bindings that are integrating two distinct (and complex) projects. The post is implying that this is a flaw in the core Yjs library and an issue with CRDTs as a whole. This is not the case. It is very true that there are nuances you have to deal with when using CRDT toolkits like Yjs and Automerge - the merged state is "correct" as a structure, but may not match your scheme. You have to deal with that into your application (Prosemirror does this for you, if you want it, and can live with the invalid nodes being removed) You can't have your cake and eat it with CRDTs, just as you can't with OT. Both come with compromises and complexities. Your job as a developer is to weigh them for the use case you are designing for. One area in particular that I feel CRDTs may really shine is in agentic systems. The ability to fork+merge at will is incredibly important for async long running tasks. You can validate the state after an agent has worked, and then decide to merge to main or not. Long running forks are more complex to achieve with OT. There is some good content in this post, but it's leaning a little too far towards drama creation for my tast.
- deleted 7mo ago[deleted]
- antics 7mo agoAuthor here, sorry if this was not clear: that specific point was not supposed to be an indictment of all CRDTs, it was supposed to be much more narrow. Specifically, the Yjs authors clearly state that they purposefully designed its interface to ProseMirror to delete and recreate the entire document on every collab keystroke, and the fact that it stayed open for 6 YEARS before they started to try to fix it, does in my opinion indicate a fundamental misunderstanding of what modern text editors need to behave well in any situation. Not even a collaborative one. Just any situation at all. I think it's defensible to say that this point in particular is not indicting CRDTs in general because I do say the authors are trying to fix it, and then I link to the (unpublicized) first PR in that chain of work (which very few people know about!), and I specifically spend a whole paragraph saying I hope that I a forced to write an article in a year about how they figured it all out! If I was trying to be disingenuous, why do any of that?
- stainlu 7mo ago[dead]
- auggierose 7mo agoAnd let's not forget that the official paper on Yjs is just plain wrong, the "proofs" it contains are circular. They look nice, but they are wrong.
- breakingcups 7mo agoCould you elaborate on that or share a source? It sounds like it'd be not just interesting but important to learn.
- auggierose 7mo agohttps://dl.acm.org/doi/epdf/10.1145/2957276.2957310 https://dl.acm.org/doi/epdf/10.1145/2957276.2957310 Try to understand 3.1-3.4 in this paper, and you'll find that the correctness proof doesn't prove anything. In particular, when they define <_c, they do this in terms of rule1, rule2, and rule3, but these are defined in terms of <_c, so this is just a circular definition, and therefore actually not a definition at all, but just wishful thinking. They then prove that <_c is a total order, but that proof doesn't matter, because <_c does not exist with the given properties in the first place.
- mweidner 7mo agoThis was my impression as well. If you ignore the paper and just look at the source code - and carefully study Seph Gentle's Yjs-like RGA implementation [1] - I believe you find that it is equivalent to an RGA-style tree, but with a different rule for sorting insertions that have the same left origin. That rule is hard to describe, but with some effort one can prove that concurrent insertions commute; I'm hoping to include this in a paper someday. [1] https://josephg.com/blog/crdts-are-the-future/ https://josephg.com/blog/crdts-are-the-future/
- josephg 7mo agoYes, I think it would be a good paper. I made a tiny self contained implementation of this algorithm here if anyone is curious: https://github.com/josephg/crdt-from-scratch/blob/master/crdt.ts https://github.com/josephg/crdt-from-scratch/blob/master/crd... FugueMax (or Yjs) fit in a few hundred lines of code. This approach also performs well (better than a tree based structure). And there's a laundry list of ways this code can be optimised if you want better performance. If anyone is interested in how this code works, I programmed it live on camera in a couple hours: https://www.youtube.com/watch?v=_lQ2Q4Kzi1I https://www.youtube.com/watch?v=_lQ2Q4Kzi1I This implementation approach comes from Yjs. The YATA (yjs) academic paper has several problems. But Yjs's actual implementation is very clever and I'm quite confident its correct.
- vinayaksodar 7mo ago[dead]
- lostmsu 7mo agoFrom the "40 line CRDT replacement": const result = step.apply(this.doc); if (result.failed) return false; I suspect this doesn't work.
- antics 7mo agoAuthor here. I'll actually defend this. Most of the subtlety of this part is actually in document schema version mismatches, and you'd handle that at client connect, generally, since we want the server to dictate the schema version you're using. In general, the client implementation of collab is pretty simple. Nearly all of the subtlety lies in the server. But it, too, is generally not a lot of code, see for example the author's implementation: https://github.com/ProseMirror/website/tree/master/src/collab/ https://github.com/ProseMirror/website/tree/master/src/colla...
- minikomi 7mo agoComponent library page in the docs gives 404
- anentropic 7mo agoI'm curious how these approaches compare with MRDTs implemented in Irmin https://gowthamk.github.io/docs/mrdt.pdf https://gowthamk.github.io/docs/mrdt.pdf
- dsnr 7mo agoIt should be noted that this is about text editing specifically, and for other use-cases YJS is using other code pathways/algorithms, but you have to be careful how you design your data structure for atomic updates.
- ralferoo 7mo agoI just read part 1 as well as part 2, for me it raises an interesting question that wasn't addressed. I correctly guessed the question posed about the result of the conflict, and while it's true that's not the end result I'd probably want, it's also important because it gives me visibility of the other user's change. Both users know exactly what the other did - one deleted everything, the other added a u. If you end up with an empty document, the deleting user doesn't know about the spelling correction that may need to be re-applied elsewhere. Perhaps they just cut and pasted that section elsewhere in the document. But there's another issue that the author hasn't even considered, and possibly it's the root cause why the prosemirrror (which I'd never heard of before btw) does the thing the author thinks is broken... Say you have a document like "请来 means 'please go'" and independently both the Chinese and English collaborators look at that and realise it's wrong. One changes it to "请走 means 'please go'" and the other changes it to "请来 means 'please come'". Those changes are in different spans, and so a merge would blindly accept both resulting in "请走 means 'please come'" which is entirely different from the original, but just as incorrect. Depending on how much other interaction the authors have, this could end up in a back and forth of both repeatedly changing it so the merged document always ended up incorrect, even though individually both authors had made valid corrections. That example seems a bit hypothetical, but I've experienced the same thing in software development where two BAs had created slightly incompatible documents stating how some functionality should work. One QA guy kept raising bugs saying "the spec says it should do X", the dev would check the cited spec and change the code to match the spec. Weeks later, a different QA guy with a different spec would raise a bug saying "why is this doing X? The spec says it should do Y", a different dev read the cited spec, and changed the code. In this case, the functionality flip-flopped about 10 times over the course of a year and it was only a random conversation one day where one of them complained about a bug they'd fixed many times and the other guy said "hey, that bug sounds familiar" and they realised they were the two who'd been changing the code back and forth. This whole topic is interesting to me, because I'm essentially solving the same problem in a different context. I've used CRDT so far, but only for somewhat limited state where conflicts can be resolved. I'm now moving to a note-editing section of the app, and while there is only one primary author, their state might be on multiple devices and because offline is important to me, they might not always be in sync. I think I'm probably going to end up highlighting conflicts, I'm not sure. I might end up just re-implementing something akin to Quill's system of inserts / deletes.
- gritzko 7mo agoThe actual point of the post: Y.js is slow and buggy.
- ghProTip 7mo agoCouldn't agree more with the gist of the argument, especially in the context of ProseMirror. That's why I created prosemirror-collab-commit.
- deleted 7mo ago[deleted]
- nchmy 7mo agoFantastic article. I was particularly interested because WordPress has been working to add collaborative editing and the implementation is based on yjs. I hope that won't end up being an issue... It would have been nice if the article compared yjs with automerge and others. Jsonjoy, in particular, appears very impressive. https://jsonjoy.com/ https://jsonjoy.com/
- chuckadams 7mo agoThe transport for collaborative editing in Wordpress 7.0 is HTTP polling. Once per second, even if no one else is editing. It jumps to 4 requests/sec if just two people are editing. And it's enabled by default on all sites, though that might not be the case when it leaves beta.
- nchmy 7mo agoThe transport is a completely different concern... (though there's also a websocket implementation). They use Yjs: https://make.wordpress.org/core/2026/03/10/real-time-collaboration-in-the-block-editor/ https://make.wordpress.org/core/2026/03/10/real-time-collabo...
- miloignis 7mo ago(Xpost from my lobsters comment since the Author's active over here): I really disagree with this article - despite protestation, I feel like their issue is with Yjs, not CRDTs in general. Namely, their proposed solution: 1. For each document, there is a single authority that holds the source of truth: the document, applied steps, and the current version. 2. A client submits some transactional steps and the lastSeenVersion. 3. If the lastSeenVersion does not match the server’s version, the client must fetch recent changes(lastSeenVersion), rebase its own changes on top, and re-submit. (3a) If the extra round-trip for rebasing changes is not good enough for you, prosemirror-collab-commit does pretty much the same thing, but it rebases the changes on the authority itself. This is 80% to a CRDT all by itself! Step 3 there, "rebase its own changes on top" is doing a lot of work and is essentially the core merge function of a CRDT. Also, the steps needed to get the rest of the way to a full CRDT is the solution to their logging woes: tracking every change and its causal history, which is exactly what is needed to exactly re-run any failing trace and debug it. Here's a modified version of the steps of their proposed solution: 1. For each document, every participating member holds the document, applied steps, and the current version. 2. A client submits (to the "server" or p2p) some transactional steps and the lastSeenVersion. 3. If the lastSeenVersion does not match the "server"/peer’s version, the client must fetch recent changes(lastSeenVersion). The server still accepts the changes. Both the client and the "server" rebase the changes of one on top of the other. Which one gets rebased on top of the other can be determined by change depth, author id, real-world timestamp, "server" timestamp, whatever. If it's by server timestamp, you get the exact behavior from the article's solution. If you store the casual history of each change, you can also replay the history of the document and how every client sees the document change, exactly as it happened. This is the perfect debugging tool! CRDTs can store this casual history very efficiently using run-length encoding: diamond-types has done really good work here, with an explanation of their internals here: https://github.com/josephg/diamond-types/blob/master/INTERNALS.md https://github.com/josephg/diamond-types/blob/master/INTERNA... In conclusion, the article seems to be really down on CRDTs in general, whereas I would argue that they're really down on Yjs and have written 80+% of a CRDT without meaning to, and would be happier if they finished to 100%. You can still have the exact behavior they have now by using server timestamps when available and falling back to local timestamps that always sort after server timestamps when offline. A 100% casual-history CRDT would also give them much better debugging, since they could replay whatever view of history they want over and over. The only downside is extra storage, which I think diamond-types has shown can be very reasonable.
- voctor 7mo agoI think Y.js 14 and the new y-prosemirror binding fix a lot of the encountered issues
- antics 7mo agoIt might fix the replace-everything bug. It definitely does not fix any of the other issues I mentioned. Even just taking the permissions problem: Yjs is built for a truly p2p topology you as a baseline will have a very hard time establishign which peers are and aren't allowed to make which edits. You can adopt a central server, but then the machinery that makes Yjs amenable to p2p is uselessly complicated. And if you cross that bridge, you'll still have to figure out how to let some clients do mark-only edits to the document for things like comments, while other can edit the whole text. That can be done but it's not at all straightforward, because position mapping is very complicated in the Yjs world.
- jerf 7mo agoBack around 2000 or 2001, I got the idea for a collaborative editor that also would have had some UI fanciness in it. I abandoned it when I couldn't find a GUI toolkit that had an acceptable level of quality for that UI fanciness, without itself becoming a multi-year project. So I never even got to the point of playing with the actual collaborative editing aspects. Having watched that space now for the last nearly 25 years... of all the projects I've abandoned over the years, that is the one that I am most grateful I gave up on. The gulf between "hey what if we could collaboratively edit live" and what it takes to actually implement it is one of the largest mismatches between intuition and reality I know of. I had no idea.
- mweidner 7mo agoThe PowerSync folks and I worked on a different approach to ProseMirror collaboration here: https://www.powersync.com/blog/collaborative-text-editing-over-powersync https://www.powersync.com/blog/collaborative-text-editing-ov... It is neither CRDT nor OT, but does use per-character IDs (like CRDTs) and an authoritative server order of changes (like OT). The current implementation does suffer from the same issue noted for the Yjs-ProseMirror binding: collaborative changes cause the entire document to be replaced, which messes with some ProseMirror plugins. Specifically, when the client receives a remote change, it rolls back to the previous server state (without any pending local updates), applies the incoming change, and then re-applies its pending local updates; instead of sending a minimal representation of this overall change to ProseMirror, we merely calculate the final state and replace with that. This is not an inherent limitation of the collaboration algorithm, just an implementation shortcut (as with the Yjs binding). It could be solved by diffing ProseMirror states to find the minimal representation of the overall change, or perhaps by using ProseMirror's built-in undo/redo features to "map" the remote change through the rollback & re-apply steps.
- antics 7mo agoHi Matt! Good to see you here. For those who don't know, Matt also wrote a blog about how to do ProseMirror sync without CRDTs or OT here: https://mattweidner.com/2025/05/21/text-without-crdts.html https://mattweidner.com/2025/05/21/text-without-crdts.html and I will say I mostly cosign everything here. Our solution is not 100% overlap with theirs, but if it had existed when we started we might not have gone down this road at all.
- mweidner 7mo agoYour part 1 post was one of the inspirations for that :) Specifically, it inspired the question: how can one let programmers customize the way edits are processed, to avoid e.g. the "colour" -> "u" anomaly*, without violating CRDT/OTs' strict algebraic requirements? To which the answer is: find a way to get rid of those requirements. *This is not just common behavior, but also features in a formal specification [1] of how collaborative text-editing algorithms should behave! "[The current text] contains exactly the [characters] that have been inserted, but not deleted." [1] http://www.cs.ox.ac.uk/people/hongseok.yang/paper/podc16-full.pdf http://www.cs.ox.ac.uk/people/hongseok.yang/paper/podc16-ful...
- Kjue 7mo agoAm I correctly understanding that you (Moment) have chosen to use Prosemirror and that with that using Yjs was the hard part? Or did you mean to say in the article that you used Yjs directly? It would be less prone to misunderstanding if it read "why we don't use y-prosemirror" and you would lose a lot of potential audience for the post. I tried to understand what was wrong in Yjs, as I'm using it myself, but your point is not really with Yjs it seems but on how the interaction is with Prosemirror in your use case. I can see why you're bringing up your points against Yjs and I'm having a hard time understanding why you don't consider alternatives to Prosemirror directly. Put another way, "because this integration was bad the source system must also be bad". I do not condone this part of your article. Seems like a sunken cost fallacy to me and reasoning about it at anothers expense, but perhaps not. Hoping to hear back from you.
- antics 7mo agoSo, we are basically making two points. First, the fact that Yjs bindings, by design (for ~6 years), replace the entire document, does in my opinion, indicate a fundamental misunderstand what rich text editors need to perform well in any circumstance, not just collaborative ones. As I say in the article... I hope to be able to write another article that this has changed and they now "get" it, but for now I do not think it is appropriate to trust Yjs with this task for production-grade editors. I'm sorry to write this, but I do think it's true! I'm not trying to bag on anyone! Second, and more material: to deploy Yjs to production in the centralized case, I think you are very much swimming against the current of its architecture. Just one example is permissions. There is no established way to determine which peers in a truly-p2p architecture have permissions to add comments vs edit, so you will end up using a centralized server for that. But that's not free, CRDTs are mechanically much more complicated! For example, you have to figure out how to disallow a user to make mark-only edits if they have "commenter" access, but allow editing the whole doc for "editor" access. This is trivial in `prosemirror-collab` (say) but it's very hard in Yjs because you have to map it "through" their XML transformations model. I'm happy to talk more about this if it's helpful. But yes, we are trying to say some stuff about Yjs specifically, and some stuff about CRDTs generally.
- 7mo ago
- useftmly 7mo ago[dead]
- karel-3d 7mo agoSorry that I am too stupid to understand what Moment is. It is a collaborative markdown file that also renders very fast. So far so good. And then... it somehow adds Javascript? And React? And somehow AI is involved? I truly don't understand what it is, and I am (I think) the end customer... edit: I tried it and I just get "Loading..." forever. So, anyway, next time.
- twhitson 7mo agoHey karel-3d, I'm one of the engineers working on Moment and would love to help figure out the issue you're running into. Would you mind reaching out via our Discord or email (trey@moment.dev)?
- karel-3d 7mo agoOK I will be happy to help. I didn't mean to be dismissive! Will ping you tomorrow
- nextaccountic 7mo agoI would like to know if you plan to open source anything, and how much. https://github.com/orgs/moment-eng/ https://github.com/orgs/moment-eng/ looks a bit empty
- karel-3d 7mo agounfortunately I cannot reproduce the "Loading..." issue. Now everything works. (I still don't fully understand Moment. But reading Agents.md ironically helps me understand it a bit.)
- torginus 7mo agoThe biggest evidence against collaborative editing working and being useful is that programmers don't use it. We go through the pain of having git branches and manual merges. We're the nerdiest bunch in the world, absolutely willing to learn and adapt the most arcane stuff if it gives us a real of percieved advantage, yet the fact that Google Docs style CRDTs have completely elided the profession speaks volumes about their actual usefulness.
- ansc 7mo ago>Google Docs style CRDTs Google Docs is OT though.
- zamfi 7mo ago> The biggest evidence against collaborative editing working and being useful is that programmers don't use it. We go through the pain of having git branches and manual merges. Hmm -- this seems a bit apples and oranges to me: collaborative editing is sync; git branches, PRs, etc. are all async. This is by design! You want someone's eyes on a merge, that's the whole rationale behind PRs. Collab editing tries to make merges invisible. Totally different use case, no?
- catlifeonmars 7mo agoCollaborative coding is a niche but possibly interesting use case. I’m thinking of notebook cells with reactive inputs and outputs. Actually not dissimilar to a spreadsheet in many ways.
- josephg 7mo ago> The biggest evidence against collaborative editing working and being useful is that programmers don't use it. We go through the pain of having git branches and manual merges. But git branches are collaborative editing! They're just asynchronous collaborative editing. It would be possible to build a git clone on top of CRDTs, which had the same merge conflict behaviour. The advantage of a system like that would be that you could use the same system for both kinds of collab. editing - realtime collab editing and offline / async collab editing. Its just, nobody has built that yet. > the fact that Google Docs style CRDTs have completely elided the profession speaks volumes about their actual usefulness. Software engineers still rely on POSIX files for local cross-app interoperability. Eg, I save a file in my text editor, then my compiler reads it back and emits another file, and I run that. IMO the real problem is that this form of IPC is kind of crappy. There's no good way to get a high fidelity change feed from a POSIX file. To really use CRDTs we'd need a different filesystem API. And we'd need to rewrite all our software to use it. That isn't happening. So we're stuck with hacks like git, which have to detect and reconstruct all your editing changes using diffs every time you run it. This is why we don't have nice things.
- rishabhjajoriya 7mo agoCollaborative editing looks deceptively simple until you deal with real-world concurrency and network issues. Operational transforms and CRDTs both introduce their own tradeoffs.
- mlysk 7mo agoGreat article - you mentioned "two most popular families of collab editing [...] OT and CRDT". One thing you should look into is the work of https://braid.org https://braid.org - Its combining crdt with ot. Work that is inspired by that build the basis of loro which allows to prune history (helping with the tombstone issue you mentioned)
- constructrurl 7mo ago[dead]
- Wazako 7mo agoAlternatively, a much simpler CRDT solution is to flatten our tree and build a LWW underneath it. This makes it easy to debug, save, and delete the history. { “id:1”: { “parent_id”: “root”, “type”: “p” }, “id:2”: { “parent_id”: “id:1”, “type”: “text”, “content”: “text”, "position": 1 } } Or internally: [ [HLC, “id:2”, ‘parent_id’, “id:1”], [HLC, “id:2”, ‘type’, “text”], ... ] Merging is easy, and allows for atomic modifications without rebuilding the entire tree, as well as easy conflict resolution. We add the HLC (clock, peer id). If the time difference between the two clocks is significant, we create a new field [HLC, id, “conflict:” + key, old_value]
- ianberdin 7mo agoYJS works perfectly. I use it for years on PlayCode. But you are talking about the specific plugin for prosemiror. Yes, here I agree: yjs core is well written, while plugins are “nice to have”.
- nickthesick 7mo agoI'm actually in the middle of rewriting the y-prosemirror binding with Kevin Jahns as we speak, we hope to address a number of the fundamental design choices that were made 6 years ago. I did a presentation on this at FOSDEM this year if anyone is interested in some specifics to the approach we are taking for this: https://fosdem.org/2026/schedule/event/8VKQXR-blocknote-yjs-prosemirror/ https://fosdem.org/2026/schedule/event/8VKQXR-blocknote-yjs-... I feel like this post is overly hyperbolic about the choices that an Open Source maintainer made years ago, and no one seemed to care enough to pay him to rewrite it.
- kevinjahns 7mo agoHi, the author of Yjs here. Thanks, Nick, for chiming in! As this article is blowing up now, I want to address a few points. I, too, feel the need for simplicity over overly complex solutions - and I found it in CRDTs. They beautifully allow me to reason about conflicts - so that my users don't have to. Very few people can design a custom conflict resolution algorithm for an application. Yjs is a general-purpose framework that enables you to make EVERYTHING collaborative. That's the goal. It's fine if you want to explore different solutions. I don't understand the need to put down one framework in favor of another. It doesn't have to be "OT vs CRDT". Hey, if you found something that works for you - great! But let me tell you that neither solution magically makes everything simpler. There is still a lot to learn. Different solutions to conflict resolution have different tradeoffs. It's unfortunate that the author of the article attributes all complexity to Yjs. It's just that collaborative editing is a very complex problem and requires a lot of attention to detail. In many regards, Yjs has done very well for the larger ecosystem. In other regards there is room for improvement. The only thing I acknowledge from the article is the criticism about y-prosemirror "replacing the whole document". Unfortunately, the author extrapolated some false assumptions. This is not a performance issue. y-prosemirror runs at 60 fps even on large documents. It's like arguing React is slow because it replaces the whole document with every edit. We leverage ProseMirror's behavior to do identity checks on the nodes before updating the DOM. However, it's true that this breaks positions for some plugins (e.g. a comment plugin). Instead of Prosemirror positions, we encourage plugins to use Yjs-based positions, which are more accurate in case of conflicts. Marijn talks about this as well [1]. The collab implementation in Prosemirror does not guarantee that positions always converge. That means, comments could end up in different places for different collaborators. This works in most cases, but in some it doesn't - which is one of the reasons why I prefer CRDTs as a framework to think about conflicts. But as Nick said, we are currently working on a new y-prosemirror binding that works better with existing plugins. I'm curious about the section "CRDTs are much, much harder to debug" which ironically talks about how hard prosemirror-collab is to debug. You won't find any such bugs in Yjs. The conflict-resolution algorithm is quite simple and has been battle tested. Before every release, Yjs undergoes extensive fuzz testing for hours in simulated scenarios. I'm very happy to show anyone how to debug a CRDT. It requires some background information, but it ultimately is easier. To address another unfounded claim by the author: I bet OP $1000 that the GC algorithm in Yjs is correct even in offline-editing scenarios. He won't be able to reproduce the issues he is talking about. [1]: https://marijnhaverbeke.nl/blog/collaborative-editing-cm.html https://marijnhaverbeke.nl/blog/collaborative-editing-cm.htm...
- scofalik 7mo agoI read both parts. Well written, I agree with a lot of stuff. I am long-time CKEditor dev, I was responsible for implementing real-time collaboration in the editor and the OT implementation. Regarding the first part of your article. Guess what - CKEditor would output "" :). And even better, if the user who deleted all does undo, you'd get "u" where it was typed originally. However, I fully agree, that for every algorithm, you will be able to find a scenario where it fails to resolve conflict in a way expected by the user. But we cannot ask user to resolve a conflict manually every time it happens. Offline editing, as you correctly observed, is more difficult, because the conflicts pile up, and multiple wrong decisions can result in a horrifying final result. I fully agree, that this is not only an algorithmic problem but also a UX problem. Add to this, that in many apps, you will also have other (meta)data that has to be synced too (besides document data). CKEditor is, in theory, ready for offline editing. From algorithm POV, offline is no different than very very very slow connection (*). In the end, you receive a set of operations to transform against other set of operations. However, currently we put the editor in read-only state when the connection breaks. We are aware, that even if all transformations resolve as expected, then the end result may still be "weird". And even if the end result is actually as expected, the amount of changes may be overwhelming to a person who just got the connection back, so it still may be good to provide some UI/UX to help them understand what happened. (*) - that is, unless the editing session on the server ended already, and, simply saying, you don't have anything to connect to (to pull operations from). Regarding OT. I have a feeling that one mistake most people make, is that they take OT as it is described in some papers or article, and don't want to iterate over this idea. To me, this is not just one algorithm, rather an idea of how to think about and mange changes happening to the data. For CKEditor, from the very beginning, we were forced to innovate over typical OT implementations. First of all we focused on users intentions. Second of all, we needed to adapt it to tree data structure. These challenges shaped my way of thinking - OT is "an idea", you need to adapt it to your project. Someone here asked if there's library for OT, because they want to use it for spreadsheets. I'll say -- write it on your own and adapt it to spreadsheets. You'll discover that maybe you don't need some operations, or maybe you need new operations dedicated for spreadsheets. This is what we ended up doing. @Reinmar already posted this link here, but we describe our approach here: https://ckeditor.com/blog/lessons-learned-from-creating-a-rich-text-editor-with-real-time-collaboration/ https://ckeditor.com/blog/lessons-learned-from-creating-a-ri.... Circling back to your example with typing and removing whole sentence. This is how you innovate over OT. To us, such deletion is not deleting N singular characters starting from position P. The intention is to remove some continuous range of text. If someone writes inside the range, it just changes the boundary of stuff to remove, but surely we don't want to show some random letters after the deletion happens. We account for that and make modifications in our OT implementation. Similarly with positions in document. In CKEditor, you can use LivePositions and LiveRanges, which are basically paths in tree data structure. Every position is transformed by operation too. Many features we have base on that. So, my take here is -- don't bash OT because you based your experience on some simple implementations. Possibly the same is with Yjs. Don't bash CRDTs because Yjs is doing something badly? And some final words regarding the second part. We also follow the same pattern as your diagram shows in "How the simple thing works" section. As I was reading through the article, and looking at provided examples, it's hard for me not to think, that what's happening is some kind of an OT-variant, maybe simplified, or maybe adapted to some specific cases. But there are strong similarities between what you described and CKEditor 5, and we use OT. Like, looking at this from top-level view, I could say, "well, we do the same". We have the same loop with conflict resolution, we just call "rebase" a "transformation", and instead "steps" we have "operations". Also, you say it is 40LOCs, but how much magic happens in `step.apply()`? How much the architecture was made to make it possible? Even Marijn makes this comment here: https://news.ycombinator.com/item?id=47409647 https://news.ycombinator.com/item?id=47409647. For comparison, this is CKEditor's file that includes the OT functions to transform operations: https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-engine/src/model/operation/transform.ts https://github.com/ckeditor/ckeditor5/blob/master/packages/c.... It's 2600LOCs (!), but at least most of it are comments :). Again, the basic idea for OT is very simple (and this implementation could be simpler, we also learned a lot in the process). It's up to you how much you want to delve into solving "user intention" issues.