Back to Blog
App DevelopmentPublished on July 3, 2026

Inside Wordgard: Architecting Next-Generation Rich-Text Editors via Immutable State Trees and Schema-Driven Document Models

Discover the architectural breakthroughs of Wordgard, the new rich-text editor from the creator of ProseMirror. Learn how immutable document states, schema constraints, and transaction pipelines solve the historical nightmares of browser-native text editing.

The ContentEditable Conundrum: Why Browser-Native Editing is Broken

For decades, web developers building rich-text editors faced a fundamental, systemic hurdle: the browser's native contenteditable attribute. When you set an element to contenteditable="true", you hand over document mutation control to the browser's layout engine. The browser becomes responsible for interpreting keystrokes, mouse clicks, and copy-paste events, translating them directly into DOM mutations.

However, different browsers handle these mutations in wildly inconsistent ways. Pressing the "Enter" key in one browser might insert a <p> tag, while another inserts a <div> or a <br>. Bold text might be wrapped in <strong>, <b>, or a <span style="font-weight: bold;">. This lack of standardization makes real-time collaboration, undo/redo history tracking, and predictable document serialization almost impossible to implement reliably.

To solve this, modern web-based editors bypass direct DOM mutation entirely. Instead of treating the DOM as the source of truth, they implement a unidirectional data flow where the editor's internal document model acts as the single source of truth, and the DOM is merely a projected view of that model. This is the design philosophy pushed to its logical extreme by Wordgard, the new in-browser rich-text editor designed by the creator of ProseMirror.


Enter Wordgard: The Architectural Evolution of ProseMirror

Wordgard represents a modern, lightweight, and highly optimized evolution of the concepts established by ProseMirror. While ProseMirror remains an industry standard for structured document editing, Wordgard refines its core primitives to leverage modern JavaScript APIs, improve typing performance on complex documents, and simplify the developer experience for building collaborative, block-based editors.

At the heart of Wordgard's architecture are three fundamental pillars:

  1. An Immutable Document Model: The document is represented as an abstract syntax tree (AST) of immutable nodes.
  2. A Schema-Driven Constraint System: The structure of the document is strictly governed by a developer-defined schema.
  3. A Transactional State Loop: All changes to the document must flow through explicit transactions, preventing unpredictable side effects.

Let us dive deep into how these architectural pillars function under the hood.


The Core Anatomy of a Schema-Driven Document Model

Unlike traditional editors that represent documents as flat HTML strings, Wordgard structures documents as a strict tree of nodes. This tree is highly structured and does not allow arbitrary nesting. It distinguishes between two primary types of structural elements:

  • Nodes: Structural blocks (such as paragraphs, headings, list items, and code blocks) or inline content (such as text or inline images).
  • Marks: Metadata attached to inline nodes to represent styling or annotations (such as bold, italic, links, or comment IDs).

This structure is strictly enforced by a Schema. The schema defines the exact relationships allowed between nodes. For example, a schema might dictate that a bullet_list can only contain list_item nodes, and a list_item must contain one or more paragraph nodes.

Here is a conceptual representation of how a Wordgard schema is defined:

import { Schema } from "wordgard-model";

const blogSchema = new Schema({
  nodes: {
    doc: { content: "block+" },
    paragraph: {
      group: "block",
      content: "inline*",
      toDOM() { return ["p", 0]; }
    },
    heading: {
      group: "block",
      attrs: { level: { default: 1 } },
      content: "inline*",
      toDOM(node) { return ["h" + node.attrs.level, 0]; }
    },
    text: {
      group: "inline"
    }
  },
  marks: {
    strong: {
      toDOM() { return ["strong", 0]; }
    }
  }
});

When a user attempts to paste content, drop an image, or press "Enter", Wordgard parses the proposed change against this schema. If the change violates the schema constraints—for example, trying to paste a heading inside another heading—the editor automatically transforms or rejects the input to maintain document integrity. This guarantees that the document state is always valid and serializable.


The Unidirectional State Loop: Intercepting Transactions

To maintain strict control over document mutations, Wordgard implements a unidirectional data flow loop. Direct modifications to the DOM or the internal document tree are completely forbidden. Instead, the application state progresses through a deterministic loop:

  1. User Interaction: The user performs an action (e.g., typing a character, pasting text, clicking "Bold").
  2. Event Interception: Wordgard intercepts the event before the browser can mutate the DOM.
  3. Transaction Dispatch: The editor constructs a Transaction describing the change (e.g., "insert text 'X' at position 12").
  4. State Transition: The current EditorState receives the transaction and computes a new EditorState containing the updated, immutable document tree.
  5. View Projection: The updated state is projected onto the DOM, updating only the elements that actually changed.
+------------------+
|   User Action    |
+--------+---------+
         |
         v
+--------+---------+
|   Transaction    |
+--------+---------+
         |
         v
+--------+---------+
| New EditorState  |
+--------+---------+
         |
         v
+--------+---------+
|    DOM Update    |
+------------------+

By routing all changes through transactions, developers can easily hook into the loop to implement advanced features like collaborative editing, real-time validation, or custom undo/redo stacks. Here is an example of intercepting a transaction to log changes:

import { EditorView } from "wordgard-view";
import { EditorState } from "wordgard-state";

const view = new EditorView(document.querySelector("#editor"), {
  state: EditorState.create({ schema: blogSchema }),
  dispatchTransaction(transaction) {
    console.log("Document steps:", transaction.steps);
    let newState = view.state.apply(transaction);
    view.updateState(newState);
  }
});

High-Performance DOM Projection and Selection Mapping

One of the most complex challenges in building a custom editor is mapping browser selection coordinates (which rely on DOM nodes and text offsets) to the editor's internal document coordinates (which treat the document as a flat sequence of integer positions).

Wordgard solves this by treating the document as a continuous linear index space. Every character, node boundary, and inline object occupies a specific numeric position. For example, in a document with a single paragraph containing the text "Hello", position 0 is the start of the document, 1 is the start of the paragraph node, 2 is the start of the text node (before "H"), and so on.

When the DOM selection changes, Wordgard performs selection mapping:

  1. It queries the browser's native Selection object to obtain the DOM container node and offset.
  2. It maps these DOM coordinates back to the linear integer index space of the immutable document model.
  3. It stores this coordinate mapping in the EditorState as a transaction.

When rendering updates, Wordgard utilizes a virtual DOM-like diffing algorithm. Instead of re-rendering the entire document on every keystroke—which would destroy input performance on large documents—it compares the new immutable tree with the old tree, identifies the specific nodes that have changed, and updates only those precise DOM elements. This ensures sub-millisecond rendering loops even in documents with thousands of words.


Collaborative Architecture: Leveraging Steps and Transformations

Because Wordgard models document changes as discrete, atomic steps (e.g., ReplaceStep, AddMarkStep), it is uniquely suited for real-time collaborative editing.

When multiple users edit a document simultaneously, their changes can conflict. Rather than sending raw HTML or whole-document states over the network, Wordgard clients transmit serialized Steps.

If User A and User B make concurrent changes:

  1. User A inserts a word at position 10 locally.
  2. User B deletes a word at position 50 locally.
  3. When User A's step arrives at User B's browser, User B's editor must adjust (rebase) User A's step coordinates because User B's deletion at position 50 may have shifted the offsets of subsequent positions.
  4. Wordgard's step mapping system automatically recalculates the position offsets of concurrent operations, ensuring that all connected clients eventually converge on the exact same document state without data loss.

Conclusion

Wordgard represents a massive leap forward in the architecture of browser-based rich-text editing. By strictly divorcing the browser's chaotic DOM mutations from the editor's immutable state, enforcing strict schemas, and implementing a unidirectional transaction loop, it provides developers with the robust primitives needed to build highly stable, collaborative, and performant editing experiences. As the web moves toward richer, more structured content creation interfaces, mastering these state-driven architectural patterns is essential for the modern frontend engineer.

#Web Development#Rich-Text Editors#JavaScript#ProseMirror#Wordgard