Un primer vistazo a Wordgard, un nuevo editor de texto
Wordgard es una nueva biblioteca de editor de texto enriquecido de Marijn Haverbeke, con transacciones de un solo cambio, correcciones, facetas y selección propia.
Wordgard es una biblioteca de JavaScript creada por Marijn Haverbeke, autor de ProseMirror y CodeMirror, para construir editores de texto enriquecido cuyos documentos se ajustan a un esquema; incluye un componente de interfaz de usuario para el editor, pero no es un editor WYSIWYG o HTML genérico y de forma libre.
Mantener una integración con ProseMirror puede implicar mapear posiciones a través de una lista de steps, o escribir un comando “genérico” que tiene que comprobar expresiones de contenido a cada paso. Wordgard es la respuesta del mismo autor a esas quejas, construida desde cero en lugar de injertada sobre ProseMirror.
Este artículo cubre lo que la biblioteca cambia: el modelo de cambios, la eliminación de las restricciones de contenido, el sistema de extensión basado en facets y la selección gestionada dentro de la biblioteca, además de dónde se sitúa una primera versión de este autor frente a ProseMirror, TipTap y Lexical.
Puntos clave
- Wordgard se publicó por primera vez como 0.1.0 el 2 de julio de 2026 bajo licencia MIT y se instala desde npm como
wordgard; el autor indicó en el lanzamiento que el proyecto permanecerá en versiones 0.x durante probablemente al menos un año. - Una transacción de Wordgard transporta exactamente un cambio, construido a partir de secciones que conservan un rango de tokens, lo reemplazan, o añaden o eliminan marcas sobre él, de modo que el rango afectado puede leerse directamente en lugar de reconstruirse a partir de una lista de steps.
- Los esquemas de Wordgard pueden restringir qué tipos de nodo puede contener un padre, pero no su orden; las correcciones (correction), funciones observadoras que devuelven especificaciones de cambio correctivas, se encargan de invariantes como las tablas rectangulares.
- La configuración es un árbol de extensiones con precedencia por valor y facets definibles por el usuario, tomados de CodeMirror 6.
- Wordgard gestiona la selección por teclado y puntero dentro de la biblioteca y dibuja su propio cursor; la selección táctil se deja al navegador.
¿Qué es Wordgard?
Wordgard es un sistema de edición de texto enriquecido para contenido que se ajusta a un esquema específico, no un componente WYSIWYG listo para usar ni una aplicación. Según la System Guide, la superficie de edición pretende sentirse como WYSIWYG, pero el contenido y las acciones de edición se nombran por lo que significan (encabezados, listas, énfasis) en lugar de por su apariencia (familia tipográfica, sangría de párrafo, negrita). La exportación principal de la biblioteca es la clase de interfaz Wordgard. Por debajo se sitúan los tipos para documentos, estado del editor y acciones de edición, y la mayoría de ellos funcionan sin necesidad de un navegador.
El anuncio de la 0.1, fechado el 2 de julio de 2026, indica la licencia MIT, el nombre del paquete npm wordgard y que el código fuente reside en la instancia de Forgejo del autor. La página del proyecto confirma la licencia y añade que los informes de errores son bienvenidos, pero que no se aceptan pull requests. La página también enumera como características los documentos basados en esquemas, las extensiones modulares, el texto bidireccional, el contenido estructurado como tablas y listas anidadas, y la edición colaborativa; conviene tomar esos puntos como afirmaciones del propio proyecto.
¿Cómo se configura un editor Wordgard?
Un editor Wordgard mínimo es una llamada a Wordgard.create con un documento, una configuración y un elemento padre. Este es el ejemplo de configuración de la guía:
import {Wordgard, menuBar} from "wordgard/editor"
import {fullSchema} from "wordgard/schema"
import {history} from "wordgard/history"
let editor = Wordgard.create({
doc: `<p>Starting content</p>`,
config: [
fullSchema(), // A predefined document schema
history(), // Enable the undo history
menuBar() // Show a menu
],
parent: document.body
})
El array config es el árbol de extensiones, y cada una de las tres entradas es un paquete de extensiones en lugar de un objeto de esquema, un plugin y un widget. fullSchema() incorpora el conjunto completo de elementos de esquema de wordgard/schema, y su propia documentación advierte que ese conjunto puede incorporar más elementos a medida que la biblioteca gane funcionalidades; los ejemplos posteriores de la guía usan basicSchema(), que agrupa un documento de bloques, párrafos, encabezados, saltos de línea y las marcas strong, emphasis y link. La cadena doc se analiza como HTML frente a ese esquema. El paquete se divide en módulos como wordgard/doc, wordgard/state, wordgard/editor, wordgard/command, wordgard/history, wordgard/schema y wordgard/types, y la guía recomienda TypeScript por lo estrechamente que encajan las piezas entre sí.
¿En qué se diferencia el modelo de cambios de Wordgard del de ProseMirror?
En Wordgard, una transacción transporta exactamente un objeto de cambio, construido a partir de secciones que conservan un tramo del documento, lo reemplazan, o añaden o eliminan marcas sobre él, de modo que el rango afectado por una edición puede leerse directamente en lugar de reconstruirse a partir de una lista de steps. En ProseMirror, una transacción es una lista ordenada de steps atómicos, cada uno actuando sobre el documento producido por el anterior, lo que obliga a que la aritmética de posiciones y la inspección de rangos recorran toda la cadena.
El razonamiento del anuncio es que el formato delta de CodeMirror, derivado a su vez de ShareJS, es a la vez más simple y más capaz. Un cambio es una secuencia plana sobre el documento antiguo. Tomemos un documento de diez tokens: añadir un token en la posición 4 se expresa como “conservar 4, reemplazar 0 por el token, conservar 6”, y poner en negrita las posiciones 3 a 6 se expresa como “conservar 3, actualizar 3 añadiendo la marca, conservar 4”. La sección de actualización de marcas es la extensión que Wordgard hace del modelo de CodeMirror.
Esto funciona sobre un árbol porque las posiciones se cuentan en tokens. En el sistema de índices de la guía, cada apertura de plot, cierre de plot, hoja no textual y carácter UTF-16 suma uno a la posición, la posición 0 se sitúa justo antes del primer hijo, y los tokens de apertura y cierre del propio nodo documento no se cuentan. Eso permite que un cambio inserte nuevas secuencias de tokens en el documento como si fuera plano, con el código de creación de cambios asumiendo la tarea de comprobar que el resultado sigue siendo un árbol bien formado.
Cuando se pasan varios cambios juntos a ChangeSet.create, cada posición se interpreta respecto al documento original y la biblioteca las desplaza automáticamente. Un cambio de marca no toca contenido alguno:
let makeStrong = ChangeSet.create(doc, {
from: 1, to: 5,
add: Strong
})
Los mismos objetos permiten transformar cambios unos sobre otros, que es la base sobre la que se construyen el historial de deshacer y la edición colaborativa.
¿Qué sustituye a las expresiones de contenido de ProseMirror?
Los esquemas de Wordgard pueden restringir qué tipos de nodo puede contener un padre, y si un plot de bloque puede estar vacío, pero no el orden en que aparecen los hijos; las expresiones de contenido tipo expresión regular de ProseMirror no tienen equivalente. El anuncio da dos razones: el código genérico de manipulación de documentos no puede escribirse frente a restricciones de orden arbitrarias sin comprobar cada operación, y las restricciones rígidas bloquean los estados intermedios desordenados por los que pasa la edición real.
Las reglas que el esquema no puede expresar se gestionan mediante correcciones. Una corrección es un observador asociado a una consulta de nodos; se ejecuta cada vez que un nodo coincidente cambia o aparece, y puede devolver una especificación de cambio que la biblioteca añade a la transacción. Al ser código, una corrección puede respetar lo que el usuario está haciendo en ese momento en lugar de rechazar mecánicamente la estructura. El ejemplo de la guía usa Correction.onChildList(Doc, ...) para insertar un encabezado de nivel 1 cuando el documento no comienza con uno; el anuncio cita las tablas rectangulares como el caso que las expresiones de ProseMirror nunca pudieron expresar.
¿Por qué Wordgard usa facets en lugar de plugins?
Wordgard sustituye el plugin de ProseMirror como unidad de configuración y precedencia por un árbol de valores de extensión de grano fino, cada uno de los cuales puede llevar su propia precedencia. La queja del anuncio es precisa: un plugin de ProseMirror agrupa varios hooks bajo una única posición de precedencia, de modo que un plugin que necesita prioridad alta para un hook y baja para otro no puede tener ambas.
En la sección de configuración de la guía, una extensión es una de estas tres cosas: un valor de uno de los tipos de extensión integrados de la biblioteca, cualquier objeto que lleve una extensión en su campo extension, o un array que contenga más de lo mismo. La precedencia explícita proviene de las funciones de GardState.prec; dentro de un mismo nivel, decide el orden en el árbol. Los facets son puntos de extensión tipados que cualquier código puede definir, con una función combine opcional para reducir las entradas a una única salida, y los compartments permiten intercambiar partes de una configuración sin descartar el estado. La palabra “plugin” no ha desaparecido: Wordgard.Plugin.define sigue presente, para objetos que mantienen su propio estado y necesitan situarse cerca del DOM, que es como están construidos los tooltips y paneles que incluye la biblioteca.
Selección dibujada por la biblioteca
Wordgard gestiona la selección por teclado y puntero dentro de la biblioteca y oculta el cursor nativo para dibujar el suyo propio, mientras que el resaltado de selección nativo permanece visible. El anuncio lo atribuye al comportamiento poco fiable de los navegadores: un cursor que no se desplaza más allá de cierto contenido, que aterriza en el lugar equivocado o que directamente no se dibuja, y una selección por arrastre de ratón que falla. Por eso la biblioteca construye su propia representación de cómo está dispuesto el contenido, hace su propio tratamiento de texto bidireccional y coloca el cursor ella misma. El ejemplo de DOM de la guía muestra un elemento dedicado como capa de cursor superpuesto al contenido, y el documento de migración indica que el resaltado nativo se mantiene porque dejarlo tal cual da menos problemas.
En el momento del anuncio de la 0.1, la selección táctil es la única excepción y sigue siendo nativa, porque reimplementarla rompe el menú contextual de la plataforma. Esa línea ha cambiado desde entonces: el changelog registra la selección táctil en la 0.5.0 para posiciones que la selección nativa no puede alcanzar, y en la 0.5.1 una posición de cursor adicional en los bordes de los plots inline, lo que da a la selección por arrastre táctil un punto donde detenerse. El anuncio también presenta el manejo de entrada como provisional: Wordgard gestiona beforeinput para todo salvo la composición y prescinde del análisis de mutaciones del DOM de ProseMirror, a la espera de pruebas en el mundo real. No se publica ninguna matriz de compatibilidad de navegadores.
Wordgard frente a ProseMirror, TipTap y Lexical
| ProseMirror | TipTap | Lexical | Wordgard | |
|---|---|---|---|---|
| Modelo de cambios | Steps ordenados | Hereda el de ProseMirror | Modelo propio | Cambio único basado en secciones |
| Forma del contenido | Expresiones de contenido tipo regex | Hereda las de ProseMirror | Modelo propio | Conjuntos de tipos de hijos más correcciones |
| Configuración | Plugins | Extensiones sobre plugins de ProseMirror | Modelo propio | Extensiones de facets con precedencia por valor |
| Selección | Nativa del navegador | Nativa del navegador | Modelo propio | Cursor dibujado por la biblioteca, táctil nativo |
TipTap es una capa de framework sobre ProseMirror y hereda su modelo central; Lexical es el framework de edición independiente de Meta. Ninguno comparte interfaces con Wordgard.
Quién debería esperar: la mayoría de los equipos, por ahora. Wordgard se publicó por primera vez como 0.1.0, y el paquete en npm ha pasado por varias versiones desde entonces; la entrada más reciente del changelog es la 0.5.2, fechada el 6 de septiembre de 2026, y el changelog registra cambios incompatibles en las versiones 0.2.0, 0.3.0, 0.4.0 y 0.5.0. El autor espera replantearse partes de la interfaz pública y permanecer en 0.x durante probablemente un año o más. No hay ruta de actualización desde ProseMirror: el documento Migrating from ProseMirror del proyecto mapea cada paquete de ProseMirror a un módulo de Wordgard y declara que no se intentó ninguna compatibilidad de interfaces.
Veredicto
Wordgard es el primer editor de la estirpe de ProseMirror que descarta los steps, las expresiones de contenido ordenadas y la selección gestionada por el navegador en un mismo diseño, y son esas tres decisiones, más que el nombre del autor, las que lo hacen merecedor de atención. Si mantienes un producto basado en ProseMirror, lee el documento de migración y las secciones de Changes y Corrections de la guía, y después prototipa como corrección una invariante de esquema que te resulte incómoda; ese ejercicio te dirá más sobre su encaje que cualquier lista de características.
Preguntas frecuentes
¿Wordgard incluye edición colaborativa o necesito construir un servidor?
Wordgard incluye una extensión de edición colaborativa del lado del cliente en wordgard/collab, pero no un servidor. La extensión collab() realiza el seguimiento de los cambios locales no confirmados; collab.sendableUpdate y collab.receive intercambian actualizaciones con una autoridad central que debes implementar tú, y collab.transformUpdate (añadido en la 0.2.0) permite a ese servidor rebasar actualizaciones obsoletas. Las correcciones omiten las transacciones remotas, así que dale a la configuración del cliente y a la transformación del servidor las mismas correcciones, enumeradas en el mismo orden.
¿Cuál es la diferencia entre un plot y una leaf en Wordgard?
Un plot es un nodo con contenido, como un párrafo, una lista, una tabla o el documento; una leaf es un nodo sin contenido, como texto, una imagen o un salto de línea. Son clases separadas, Plot y Leaf, y las propiedades isPlot e isLeaf permiten discriminar entre ellas en TypeScript. Una leaf es su propia etiqueta (tipo, parámetro, marcas), mientras que un plot contiene una etiqueta más un array de contenido.
¿Puedo crear o modificar documentos de Wordgard fuera del navegador, por ejemplo en Node?
Sí para el modelo de documento, no para el editor. Los módulos wordgard/doc, wordgard/state y wordgard/types están diseñados para ejecutarse sin DOM, de modo que puedes construir documentos, aplicar conjuntos de cambios, ejecutar correcciones y serializar a JSON en el servidor; wordgard/types depende únicamente de wordgard/doc. wordgard/editor se carga fuera del navegador pero no hace nada útil, y un documento en forma de cadena HTML necesita el parser del navegador, así que pasa JSON o usa jsdom.
¿Wordgard admite tablas y cómo las mantiene rectangulares?
Sí. El módulo wordgard/table exporta un paquete de extensiones tables() que añade los elementos de esquema de tabla, un tipo CellSelection para seleccionar rectángulos de celdas, manejadores de pegado y arrastre, un menú de tabla y tables.correction, una corrección integrada que repara tablas cuyas celdas no se alinean formando un rectángulo limpio. Sus opciones son headerCells, cellSpanning y cellContent (inline o block). Las celdas combinadas usan las marcas RowSpan y ColSpan.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k