Why we spend > $10k/yr supporting react-prosemirror, and you should too
This week we wrote some not-positive stuff about CRDTs and Yjs in particular, and it got popular on HN. I messed this up: there is a fix, it's to give money to `react-prosemirror`, and I want to convince you to do it... but this is also not an ordinary “fund OSS” pitch, and to understand why, I need to explain to you how excruciatingly bad the state of the world is right now.
The problem is not just that text editors are hard. Everyone already knows they are hard. The problem is that, somewhat by accident, the current default stack for building text editors is completely architecturally unsound, and we are handicapping an entire generation of apps with it.
Look at the last generation of companies as a baseline: it took Coda and Notion 4 years and millions of dollars each, just to get to market. This generation, it took us 4 years and millions of dollars to get to market.
Now everyone suddenly has an intense interest in text editors, because AI consumes Markdown. And, as someone who has fought this fight, it is my professional opinion is that if we do not fix these issues, this generation is going to have an even harder time than we did. With a little investment now, we fix this problem before it becomes really serious.
What broke?
Yes, hard as it is to believe, in some ways, the state of the standard text editor stack is as bad as it has ever been. Mostly for two reasons:
First, the then-nascent-now-very-popular React is fundamentally, architecturally incompatible with the view layer of the then-nascent-now-very-popular rich text editor library, ProseMirror.
Second, the dominant text editor SDK, Tiptap, integrates React and ProseMirror in a particularly naive, fundamentally unsound way.
As it happens, Shane (who co-leads `react-prosemirror`) has written about this in depth. But the short of it is this. Both React and ProseMirror expect to “own” the DOM: React accepts changes and updates its DOM in the next render cycle; ProseMirror accepts changes and immediately updates its DOM. And because ProseMirror updates change the DOM instantly, there is no "safe" time to do it: anything that read the DOM before will now be out of date, and there is no principled way of re-running those reads in general. This is why, in Shane's example, you'll see that the tooltip gets "stuck" in a previous read.
React expects the DOM to be all updated, all at once. It's a simple thing, and it seems innocuous, but it is a core assumption for a reason: when you break it, all kinds of bad things start to happen. Cursors jumping around, sporadic data loss, “Transaction mismatch” errors that crash the app, the editor getting “stuck” in some weird state that causes text to be inserted in weird spots as you type, custom plugins and NodeViews getting into odd states, getting their state blown away, or attempting to update seemingly nonsensical node ranges. All of these are symptoms.
As the problems metastasize (and they will for large codebases), you will likely find yourself calling `setTimeout` and `queueMicrotask` as a kind of technical the crystal therapy, hoping this wards off the ghosts which now very clearly hauntthe app. And, sadly, you'll find that it won't work.
This is fundamental. You can't hack around it, you can't fix it from the outside. All hand-rolled ProseMirror integrations, and all running instances of Tiptap have this problem. If you haven't noticed, just wait for your app to get more complicated.
The solution
There is only one viable way to fix this: React has to own the DOM updates. That is what `react-prosemirror` does. It implements the ProseMirror `EditorView`, entirely in React, DOM update algorithm and all. It is much, much more reliable, and much better in general.
It is more than it seems. With a reliable substrate for editing, an entire world opens up to us. Finally: reliable, turnkey collaborative editing. (It is not reliable now, as we argue here.) Access to the vast ecosystem of ProseMirror plugins... but they actually work now, without compromises, and without caveats like “only works when you're not using Yjs” (this is an example of a real-world caveat).
The team working on it
`react-prosemirror` was written at the New York Times, to power their internal CMS, called Oak.
It was open sourced, and it now lives at @handlewithcare/react-prosemirror.
How you can help
`react-prosemirror` is wrapping this work up into an umbrella project called Pitter Patter. It is here.
You can sponsor the project. Sponsor it here: https://handlewithcare.dev/pitter-patter/
They are 39% towards their goal.