Marijn Haverbeke releases his 6th text editor — a ground-up rewrite addressing nine years of architectural regrets. Delta-format changes, composable schemas, and custom selection.
On July 2, 2026, Marijn Haverbeke — the creator of ProseMirror and CodeMirror, two of the most influential text-editing libraries in the JavaScript ecosystem — announced a new project called Wordgard. It is, by his own count, the sixth non-trivial editor he has built.
Wordgard is not a ProseMirror 2.0. It is a complete ground-up redesign that incorporates everything Haverbeke has learned since stabilizing ProseMirror nine years ago, plus architectural insights from the CodeMirror 6 rewrite. The programming interface is built from scratch with zero compatibility concerns.
This article provides a technical deep dive into Wordgard's architecture: the delta-format change model that replaces ProseMirror's steps, the composable schema system, the removal of regex-based content constraints, the CodeMirror-style facet extension system, and the novel approach to browser-independent selection handling. If you work with rich text editing or follow JavaScript library design, this release is a significant event worth understanding.
ProseMirror is not going away — Haverbeke will continue to maintain it. But nine years of real-world use have revealed architectural decisions that he now considers mistakes. Instead of trying to fix them in a backwards-compatible way (which would produce, in his words, "a compromised win32-style mess"), he chose a clean break.
The key problems Wordgard addresses:
ProseMirror represents changes as a sequence of steps, where each step operates on the document produced by the one before it. Steps are atomic, each doing one clear thing, but they are "seriously awkward to work with," as Haverbeke puts it. To figure out what range of the document was replaced, you have to iterate through the step sequence in both directions, mapping positions forward and backward.
Wordgard replaces this with a delta format derived from ShareJS and refined in CodeMirror 6. A change is a flat sequence of sections, each of which either:
For a flat document of length 10, inserting the letter "L" at position 4 is represented as:
[keep 4] [replace 0 with "L"] [keep 6]
Deleting the first two characters:
[replace 2 with ""] [keep 8]
The update section is a new concept not present in CodeMirror's plain-text delta. It preserves the structure of a section but adds or removes marks (emphasis, link style, image alt text). Making the word from positions 3 to 6 bold:
[keep 3] [update 3 +bold] [keep 4]
Because Wordgard uses a token-counting indexing system (same as ProseMirror), the delta format addresses the document as a flat sequence of tokens — node open/close tokens and leaf tokens — into which it splices new sequences. This works for rich content because the editing system ensures structural validity through fix-up mechanisms.
These deltas can be easily combined: a single transaction always has one associated change, which is easy to inspect and reason about. They also support a limited form of operational transformation (OT), making it possible to merge changes described in terms of the same start document. This enables:
The document is not really a flat sequence of tokens — the tokens only make sense when they combine into a well-formed tree. If you delete a node closing token, the document becomes unbalanced and the change cannot be applied directly. Wordgard's change creation code includes checks and corrections to ensure all changes produce valid document structure.
For operational transformations, the challenge is more subtle: transforming change A to apply after change B must produce the same result as transforming B to apply after A (convergence). Wordgard solves this with a fix-up change mechanism. When transforming changes, the system derives a correction that is applied to both transformation results equally, ensuring both paths converge on the same valid document.
In ProseMirror, document schemas specify relations between nodes in a direct way — node and mark types live only within a given schema. This makes it hard to compose schemas or share functionality across them.
Wordgard takes a fundamentally different approach: node and mark types are independent entities that can be part of multiple schemas. They serve as typed, autocompletion-supporting handles that represent document elements independently of any schema context.
Composing a schema is done by throwing together the elements you need:
import { schema } from "wordgard"
import { heading, paragraph } from "wordgard/block"
import { em, strong, link } from "wordgard/mark"
const mySchema = schema([heading, paragraph], [em, strong, link])
When you need to override relationships on existing elements, a schema can specify these overrides. The node or mark definition specifies its default content or target types, but a schema that needs different behaviour can change them without affecting the base type.
This design makes reusable extensions dramatically more viable. In ProseMirror, the schema was so generic that code was either specific to a schema or couldn't know what elements meant. In Wordgard, the built-in nodes are useful out of the box, and extensions can work with them directly.
Another improvement: in ProseMirror, node attributes (like text alignment or alt text) are defined directly on the target node type. Adding alignment to every text block meant modifying each node. Wordgard generalizes marks so they can be used for these purposes, keeping node types clean and allowing modular addition of styling attributes.
ProseMirror's signature feature — the ability to specify allowed content for a parent node using a regular expression — is removed in Wordgard. A content description can only constrain what types of children are allowed, not their order.
The rationale is twofold. First, regex-based constraints make generic document manipulation code extremely difficult. Code that isn't written for a specific schema can assume almost nothing about what transformations are valid, requiring checks against the content expression for every operation. Even Haverbeke himself admits to getting this wrong in ProseMirror code.
Second, hard-locking document shape often harms the user experience. Documents are edited through small incremental actions that build up to the intended structure. If the editor blocks intermediate states that leave the document in an unusual shape, users get frustrated.
Wordgard replaces this with an abstraction called a correction — a programmatic function that fixes undesired document shapes. Corrections are superior because:
import { correction } from "wordgard"
const tableCorrection = correction((doc) => {
// Ensure all table rows have the same number of cells
// ProseMirror's content expressions couldn't express this
})
ProseMirror's extension system allows plugins that affect the system, but each plugin does multiple things (event handlers, state fields, view decorations). Plugin precedence is coarse — you can change the order of plugins, but a single plugin might need high precedence for one of its features and low precedence for another.
Wordgard copies CodeMirror 6's facets system. Facets are typed extension points that can be defined by any code, not just the editor library itself. Every extension value sets its own precedence category, independent of other features provided by the same extension bundle.
A configuration is not an array of plugins but a tree of extensions, each of which can define event handlers, configure editor attributes, add editor state, or introduce new extension points. A feature implementation typically consists of a group of extensions that work together:
import { extensionGroup, keyBinding, stateEffect } from "wordgard"
const boldFeature = extensionGroup(
keyBinding({ key: "Mod-b", run: toggleBold }),
stateEffect("bold", (state) => ({ ...state, bold: !state.bold })),
// More extensions that compose together automatically
)
The primitives that extensions build on are designed to compose cleanly. You can drop extension bundles into your configuration and expect them to work together without conflicts. This is a significant quality-of-life improvement over ProseMirror's plugin system.
A major source of ProseMirror's issues is its reliance on the browser's native selection implementation. The idea was reasonable — cursor motion in bidirectional text with styled content is hard — but in practice, browsers do "not do as good a job as hoped."
Wordgard takes a different approach: it implements almost all pointer and keyboard-based selection itself. This involved:
The one exception is touch selection, which remains native because reimplementing it breaks the native context menu — essential on phone and tablet systems. Fortunately, touch selection tends to have fewer strange misbehaviors than keyboard selection.
Browser support for editing events — particularly beforeinput — has improved
significantly in the past nine years. Wordgard handles beforeinput events for
everything except composition text input, avoiding the entire class of workarounds that
ProseMirror needs for DOM change monitoring and parsing.
Wordgard 0.1 is available on npm as wordgard. The core interface supports
almost everything Haverbeke intends, and a set of extensions confirm the design is practical.
Documentation — including a complete reference manual — is available at
wordgard.net.
The library is released under the MIT license. Haverbeke seriously considered a more restrictive license but decided that his working model is one of abundance — providing enough value that even a small fraction returns to him as a living. He explicitly denounces AI training on his code but acknowledges there's little he can do about it while keeping the system accessible.
In a notable departure from standard open-source practice, Wordgard does not accept pull requests. Haverbeke finds reviewing and negotiating large changes more work than implementing them himself, and with AI-generated code making submission volumes even higher, he's chosen to develop the project by himself.
The plan is to stay on 0.x versions for at least a year to gather feedback, fix bugs, and refine the public API. As with any 0.1 release, production use should be approached with caution — but for experimentation and early adoption, Wordgard is further along than Haverbeke's previous projects were at announcement time.
Wordgard represents a significant milestone in JavaScript text-editing library design. Haverbeke has taken everything he learned from two of the most widely used editor libraries in the ecosystem and distilled it into a cleaner, more composable architecture.
The delta-format change model, independent schema elements, corrections system, and CodeMirror 6-style facets each address a specific pain point from ProseMirror's nine-year history. Collectively, they represent a mature design philosophy that prioritizes developer ergonomics and composability over theoretical purity.
For developers building applications that need rich text editing — content management systems, collaboration platforms, knowledge bases, or structured document editors — Wordgard is worth evaluating. It's early, but the foundation is solid, and the design decisions are well-documented and well-reasoned.
If you're considering a web development project that involves rich text editing or any other complex frontend challenge, reach out to me — I'm a full-stack developer with experience across the JavaScript ecosystem and can help you choose the right tools for your specific needs.
Tell me about your project — I'll help you choose the right approach and architecture. Free initial consultation.