Hacker News new | ask | show | jobs
by nickthesick 88 days ago
I'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-...

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.

3 comments

I watched the presentation, but I'm getting the impression that you're still using a hierarchical XML-like structure for the document, which sounds like it still cannot merge or split nodes (without recreating the content after the merge or split point). Will issues like https://github.com/yjs/y-prosemirror/issues/205 be addressed by this? (Which not only breaks ProseMirror's position mapping, but also your own, and which I consider a serious enough bug that recommending y-prosemirror for anything feels unserious.)
Yes, we are still using XML to represent the document content, to represent the document as a flat list would require some research into something like Automerge's Block markers https://automerge.org/docs/reference/under-the-hood/rich-tex... which we don't have the funding to look into right now, but hope to work towards in the future.

We will have better support for ProseMirror's position mapping, but I'm not confident that we will make any progress on this specific issue, since it requires a much larger refactor than what we are contracted for (suggestions & versioning). For selection mapping specifically, we can of course make an effort here to fix it, but the underlying limitations of the split node operations would still be there.

I believe that node-splitting in y-prosemirror can be solved.

But I hope that you also can appreciate that mapping prosemirror to a CRDT structure is a very complex thing to do. Your schema implementation and node-splitting behaviors are extremely hard to map to existing conflict resolution libraries.

Other editors, like Quill, map better to CRDT structures.

This is, of course, not your problem. But if you - or any editor author for that matter - ever end up creating another editor library, I'd love to work with you on adaptability to existing conflict resolution libraries. There is a lot to gain from being compatible to the wider ecosystem.

After all, we shouldn't expect our users to set up a tailor made backend for each single collaborative component.

Encrypted editing apps like Proton Mail Docs wouldn't be possible with your solution.

I'll be the first to admit that ProseMirror's change representation is a bit messy, but I don't think that's the root of the issue here. The problem is about generalized tree-structured document representation (as opposed to the flat-string model of Quill). I believe such a representation has value, and is the appropriate choice for a system like ProseMirror or its successor. Joining and splitting blocks is not a weird quirk of ProseMirror—it is a basic, essential editing operation. But there doesn't appear to be a known appropriate way to map that structure to a CRDT in a way that can properly express such splitting and joining. And that's fundamentally limiting the approach taken in y-prosemirror.
This is not a fundamental issue. Node splitting can be represented in CRDTs. It's just really hard to map correctly to ProseMirror as the merge logic is complex and bound to a schema.

I don't blame you for ProseMirror for being how it is. I'm just offering my feedback for your next editor library.

But Kevin, you never really answered the question of the article. Unless I need a truly-masterless p2p topology, why would I do all this stuff, including throw away editor intent around things like block split, just to use Yjs? prosemirror-collab and prosemirror-collab-commit already seem to do all the things the Yjs docs claim to do (unbounded offline writes that reconcile automatically, optimistic updates, tolerant of all kinds of failures), and they work with 100% fidelity to the underlying model. AFAICT, the only thing that you need Yjs for, is true p2p editing.

This is a serious question, and the question of the article. I am here to learn what you mean, please explain.

Yjs is about making things easy. It is a good abstraction to make anything collaborative (not everyone can implement something like prosemirror-collab).

I'd take the slight performance overhead any day if I get guaranteed syncs. Network protocols are not as reliable as you think they are. Detecting random drops of messages is hard. At scale, you are going to appreciate the sync guarantees.

prosemirror-collab doesn't give you offline editing either. Because, guess what - if there is no central server you can't edit the same doc from multiple tabs.

I once had a customer that accidentally deleted part of their database containing Yjs docs. Few of his users noticed, because their docs synced through y-indexeddb.

And it's fun. You can Yjs on anything. There is a company that syncs Ydocs through QR codes.

As a generic collab library, it does a very good job. CRDTs really are a fun thing to use. A lot of people feel that way.

If you want to use something else, that's totally fine! Write an article about how great prosemirror-collab is.

The y-prosemirror rewrite is super exciting!

To speak on yjs: We use yjs over at LegendKeeper. We're not a huge app, but our users do worldbuilding for D&D, and have amassed over 30+ million collaborative documents, ranging from rich text to fantasy maps to fantasy timelines. Is yjs technically overkill when you have a central tie-breaker? Sure, but the DX is fantastic, and personally I love the idea of my application being truly local-first, even if our core value prop is not necessarily tied to being offline. It also gives me a legacy support plan for our users in case I ever get hit by a bus. :)

On the tech side, you save a lot of cognitive overhead when you can just do:

applyUpdate(docA, update1) applyUpdate(docB, update1)

and now docA and docB are in the same state, no matter what the context. For centralization, adding in a "well, they'll converge once we add a third party" absolutely increases the cognitive complexity of reasoning about your code, and limits your ability to write clean tests. Centralization buys you a lot, too. I don't think one is correct over the other.

There are tradeoffs. There's a memory and CPU cost, and yes, sometimes the "Technically merged state" of a yjs-prosemmirror document is not what's expected. Over seven years and 150,000+ users, we've never had a single person complain about it.

Hi, 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.htm...

> I don't understand the need to put down one framework in favor of another.

I didn't take the article as "putting down Yjs", just suggesting it's not the best solution for ProseMirror-backed product use cases patterned after their own.

> y-prosemirror runs at 60 fps even on large documents

Did the OP claim Yjs was slow? Have you created a ProseMirror-backed product of the complexity of Confluence's editor with 16ms frame time targets? The challenge isn't the collab algorithm as much as the CPU time of the plugins, "smart" nodes, and other downstream work triggered by updates. It's incredibly useful to have control over the granularity of updates and that is IMHO easier when dealing more closely with ProseMirror steps and transactions .

> I bet OP $1000 that the GC algorithm in Yjs is correct even in offline-editing scenarios.

Did the OP claim the Yjs algorithm is incorrect?

> You won't find any such bugs in Yjs. The conflict-resolution algorithm..

I didn't take the debugging section to indicate an issue with Yjs convergence.. Nor do most people encounter "bugs" with prose-mirror collab; last update 3 years ago? The debugging challenge is typically around "how did we arrive at this document state"(what steps got us here, where did they come from) and how steps/updates interact with plugins. IMHO discarding the original steps and dealing with a different unit of change complicates that greatly. Especially when dealing with a product in production and a customers broken document that needs to be fixed and root caused..

Hi Kevin, author here, are you sure we are on the same page about what I'm saying in this article?

For example, the debugging section is NOT about debugging problems inside Yjs, it's about debugging one's own bugs while one is using something like Yjs or prosemirror-collab. I'm normally a happy gambler but I actually think the Yjs GC algorithm probably does do what it says... indeed, my complaint is that I specifically do not want that behavior. :)

Likewise, the point I'm making about performance is NOT that Yjs is "slow". I'm saying that it's empirically very challenging to meet the 16ms perf budget even in the simplest possible realistic collab scenario... and that because of this, it is (1) very unappealing and (2) in our experience, challenging to attempt to do this when you also have to do a pile of extra things that are unrelated to the task at hand, like translate `Transaction` to and from operations on an XML doc, and deal with all the consequences of messed up positions passed to plugins. I do understand you have your own benchmarks that give you the confidence to (without qualifications) claim that y-prosemirror "runs at 60fps". You really are not curious about why we think that's not the case?

If we can't get to a shared understand what is being said here, it's going to be very hard to talk about it at all. And on the two material points at stack in your response, I believe you have the precise opposite understanding of what was written. I'm happy to keep discussing but it feels like we're starting from scratch after this response, again.