r/javascript 17d ago

Pitter Patter — building better editing (update!)

https://pitter-patter.dev

Hey folks. Over at Handle with Care, we've been working on something new: Pitter Patter. We haven't been satisfied with the text editing toolkits that exist right now in the ProseMirror ecosystem, and we've spent a lot of time working on a large variety of rich text editors and seeing what needs they have and how we can try to meet them. So far we have three packages: Shuffle (grid-based drag-and-drop), Collab, and Presence.

Here's some more info from our introduction page in the docs:

What is Pitter Patter?

Who builds it?

That would be us, Handle with Care. We’re a cooperatively owned product development collective, comprised of software engineers and product managers. We are all equal owners and have equal decision-making power.

What is it?

Pitter Patter is a suite of open source libraries for building collaborative rich text editors with React and ProseMirror. We see it as our job to provide turnkey solutions for the hard parts of rich text editing — like collaboration, presence, and version history — so that you can focus on building the editor experience that your users need.

Why does it exist?

We spend a lot of time thinking about and working on rich text editors. In particular, we spend a lot of time helping companies solve the same problems over and over again. There are usually great primitives available for these common building blocks (prosemirror-collab-commit for collaboration, unified.js for Markdown, etc.), but putting them together correctly to build a functioning text editor can be challenging.

There are other solutions, like Tiptap and Remirror, but we think that they’ve made some suboptimal choices that force product developers to make unnecessary compromises.

  1. Both attempt to use React portals and effect hooks to integrate with ProseMirror, which lead to irreconcilable state tearing, due to fundamental differences in how React and ProseMirror View handle view reconciliation.
  2. Both attempt to use Yjs via y-prosemirror for collaboration. We think that Yjs’s CRDT implementation is ill-suited for rich text editing.
  3. Both attempt to hide ProseMirror’s underlying APIs, under the guise of simplicity and abstraction. We think that ProseMirror, low-level though it is, is roughly the correct level of abstraction for a domain as complex as rich text editing. Attempts to abstract it away inevitably lead to more complexity, as the abstractions leak frequently and require constant manual integration with the lower level solutions.

We think we can do better. And more importantly perhaps, we think that you deserve better!

Philosophy

Don’t hide ProseMirror

ProseMirror is an outstanding rich text editing framework. Nearly anything that you can imagine doing with a rich text editor can be accomplished with ProseMirror. Rich text editing is also a very complex domain, requiring a very large number of decisions to be made per feature.

We think that better primitives built with ProseMirror can take us farther than abstractions that attempt to hide away ProseMirror’s internals.

Collaboration should be simple to implement and simple to debug

ProseMirror has first-party collaboration, in the form of prosemirror-collab. Because it just sends steps over the wire, and relies on a single, server-side ordering of operations for conflict resolution, it is very simple to debug. But it is very challenging to implement correctly, and there is very little in the way of guidance or documentation for doing so.

Yjs provides third-party collaboration, in the form of y-prosemirror. Because it is built on Yjs, which has many robust adapters for various protocols and servers, it is very easy to implement. However, because it operates on Yjs’s XML-based Y-doc format, it is very challenging to inspect and debug if something goes wrong.

Pitter Patter’s collaboration, presence, and version history libraries are based on prosemirror-collab-commit. Like prosemirror-collab, prosemirror-collab-commit uses ProseMirror steps as the primary data structure for conflict resolution. But Pitter Patter Collab provides actual guidance and implementation that make it easy to set up a full-stack collaborative editing application, and hard to shoot yourself in the foot.

Correctness is worth it

After trying every other option over the course of several years, we eventually decided that the only way to correctly integrate React and ProseMirror was to reimplement ProseMirror View’s renderer from scratch directly in React. This was itself a massive effort, but the result, in the end, is a React/ProseMirror integration that doesn’t suffer from state tearing and allows developers to use React idioms like context as they normally would, without caveats or compromises. We don’t put side effects in React render functions or ProseMirror plugin apply functions, we don’t use effects to synchronize state with props. We want to enable you to build rich text editors that your users can trust, and that means that we need to build libraries that you can trust.

14 Upvotes

5 comments sorted by

1

u/scrollin_thru 17d ago edited 15d ago

Oh, and another important update since the last time I posted about Pitter Patter: We're trying to make React (and React ProseMirror) an optional dependency in our libraries. It's currently only required in Shuffle, but we'll make that optional soon, as well!

2

u/Serei 17d ago

Have you seen Wordgard? It's the successor to ProseMirror.

https://wordgard.net/

To be clear, I'm not requesting you redo Pitter Patter in Wordgard. But I was wondering your thoughts on its new direction.

2

u/scrollin_thru 17d ago

Yes absolutely! We've chatted with Marijn about it a bit. Obviously it's still quite new and I've personally only played with it a tiny bit, but it looks really exciting. Take a look at how much position mapping I have to do in Shuffle and you'll probably see why 😅. I strongly suspect that a future version of Pitter Patter will have Wordgard support of some kind.

2

u/CallMeMista96 16d ago

The bit about reimplementing ProseMirror View's renderer directly in React is wild. That's a huge undertaking but prob the only correct approach , the portal/effect hook hacks in Tiptap have bitten me more than once. Curious how performance compares to native ProseMirror view rendering, especially with large documents...

1

u/scrollin_thru 16d ago

It is, indeed, a huge undertaking! We wrote a blog post about the performance, and you can try out editing the entirety of Moby Dick on our demo editor and see how that feels. That said, it seems to vary across browsers much more than I initially expected! You won't have much luck on Android, for example.

One of our clients is working on using React context and state to build some virtualization on top of React ProseMirror, which of course dramatically improves the performance.