Tiptap ProseMirror Yjs TypeScript

Tiptap Tracked Changes

Lead Engineer 2026

A ProseMirror-powered change tracking system for collaborative document editing.

Early access launch

I released Tracked Changes as an invite-only early access in early 2026. It was one of the most sophisticated features I’ve had to build — and also one of our most requested features. Tons of people needed it, from simple editing apps to B2B businesses managing legal documents and contracts.

The requirements were steep:

  • A proper suggestion mode that feels native to the editing experience
  • Reliable tracking on both inline and block-level content
  • Support for complex structures like nested lists and tables

How it works

On the ProseMirror side, I intercept incoming transactions, check if they’re collaborative, and if not — I analyze them. I take the steps (the parts of a transaction that describe what’s happening), convert them to TrackedStep objects, and start building semantic events out of those steps.

Those semantic events are handled by a lot of code. Hundreds of utilities to handle every kind of edge case imaginable.

The hard part

The hardest part was getting this all right in the frontend without requiring any diffing — so users can experience it in real time. No server-side diff passes, no post-processing. Every change is tracked as it happens.

The edge cases were relentless:

  • Deletions across insertions
  • Deletions inside deletions
  • Additions that range into deletions
  • Replacements that span tracked and untracked content
  • Lifting and wrapping/unwrapping nodes
  • Moving nodes from A to B without stable identifiers

In the end, I was able to find logical solutions for all of these. Every document transformation in ProseMirror can be intercepted, analyzed, and turned into a meaningful tracked change.

Beyond the core

On top of the tracking engine, I built a bridge via editor events that connects Tracked Changes to the Comments extension. Users can reflect suggested content as comment threads — to accept, reject, and discuss changes collaboratively.

I also built a comprehensive set of utility functions that let users:

  • Accept or reject suggestions by user, range, selection, document-wide, or by ID
  • Query and extract suggestions programmatically
  • React to suggestion changes in the UI

All of it headless — bring your own UI.