Skip to content
XTXP

Prior art

XTXP borrows from more than twenty years of work on collaborative editing, messaging and identity. Each section says what we take from it and where it shows up in the spec.

The first browser (1990) was also an editor, though only for local files, since HTTP PUT wasn’t implemented yet. The read-write web XTXP aims for. See North star.

An experimental Jabber proposal for streaming document updates over XMPP, aimed at replacing HTTP, developed in conversation with Jabber folk such as Peter Saint-Andre. Part of the line XTXP comes from; see North star.

Ink & Switch’s case for collaborative software that works offline, keeps data on the user’s own devices and syncs through CRDTs instead of a central server. XTXP’s peers, sessions and pure engine follow it.

“Surprise, it’s server rendering” and declarative partial updates

Section titled ““Surprise, it’s server rendering” and declarative partial updates”

Schalk Venter’s PyCon ZA talk on browser features that make server-rendered sites feel live without a JavaScript framework: CSS View Transitions, the Popover API, Speculation Rules and declarative partial updates, which let HTML stream into the page out of order and fill marked placeholders later, with no script. The debate it started, about streaming site content without JavaScript polyfills, led to XTXP.

A 2004 project to turn Inkscape into a shared whiteboard. It sent changes to Inkscape’s XML document over a custom Jabber namespace to other Inkscape instances. It lived in a branch, was disabled in Inkscape 0.47 because it was broken, and never synchronised defs or metadata.

Bob Jamison, who wrote Pedro (the XMPP library Inkboard used), later said he had misconceptions about XMPP as an XML transport. He had ideas for carrying an XML fragment without damaging it in transit, and favoured decoupling the XMPP agent from Inkscape.

Its code is a starting point for the Inkscape adapter: see Start from Inkboard’s code.

Lessons, and where they show up: keep the transport outside Inkscape’s internals (The Inkscape fork); don’t let XML payloads depend on XMPP framing (The Wire); don’t let a fork rot.

An XMPP extension for synchronising XML documents between clients. Worth reviewing before the patch envelope is fixed.

YATA orders concurrent insertions in a sequence. Yjs implements it, including XML element and fragment types, and Yrs is its Rust port. Xmesh v1 is built on Yrs (decision 13). Xmesh v1 uses Yrs’s XML types directly. Neither Yjs nor Yrs has a native tree move, which is the main thing a later tree layer of our own would add.

Yrs already reaches every place XTXP runs: yffi exposes it to C, and it compiles to Wasm. Xmesh follows the same patterns for its own bindings.

Yrs is released steadily; Xmesh uses 0.28 (September 2026). Fixes since 0.19 matter to XTXP: a delete set lost after applying an update (0.27.2), a missing right neighbour when inserting at index 0 (0.27.3), a swallowed pending update (0.27.1), and decoders hardened against malformed updates (0.28.0). Version 0.27 dropped Yrs’s array move, which XTXP never used. Building Xmesh found that 0.28 can still leave a pending update stuck after everything it depends on has arrived; Xmesh re-applies such updates until nothing is missing.

Evan Wallace’s mutable tree hierarchy gives each node a map of every parent it has had, with counters; the highest counter wins, and nodes caught in a cycle are reattached deterministically when the tree is read. It needs only ordinary map writes, so it fits Yrs. One basis for a later tree layer of our own; v1 moves are copies. The yrs_tree crate implements it on Yrs.

Kleppmann, Mulligan, Gomes and Beresford, A highly-available move operation for replicated trees (IEEE TPDS, 2021), solves the same problem by undoing and replaying operations in timestamp order.

Loro’s write-up compares movable-tree approaches.

A Rust CRDT library with a native movable tree. Xmesh builds on Yrs instead, for the larger Yjs ecosystem and Yrs’s existing C bindings.

Peers exchange state vectors, then send each other only the missing updates, over any transport, typically a plain WebSocket. XTXP’s reconnect sync follows the same two-step shape, and keeps cursors and presence (“awareness”) separate from document updates, as Yjs does.

An open-source Rust server for Yjs documents, built on Yrs, from Jamsocket (MIT). It persists each document as one snapshot to a filesystem or S3-compatible storage, and controls access per document with bearer tokens, full or read-only. It runs as a binary; its Wasm build for Cloudflare’s edge is deprecated. Its core authors stopped committing in 2025, and v0.9.1 (September 2025) is the latest release.

It runs Yrs 0.19.2, which loses a delete that arrives before the insert it deletes, so deleted content can come back when updates arrive out of order. XTXP borrows from y-sweet rather than extending it, and runs M1 and M2 on it as is first (decision 25).

The Android XMPP client at conversations.im. It shaped XTXP’s early design, and its practices now live in the XMPP bridge: stream management, archive, client state and push for unreliable mobile links, and end-to-end encryption in the chat stream.

In both, a public key is the identity, with no central account. XTXP takes the same view (Identity and access).

User-Controlled Authorization Networks: permissions granted to public keys and delegated by signed tokens, with no central server. A reference point for grants and delegation if XTXP adds an admin role.

Research on access control for local-first software: groups, delegation and revocation that work without a central authority, alongside CRDT sync. The closest prior art to XTXP’s grants and revocation, and worth reading before the details are fixed.

The PRF extension lets a passkey derive a secret that can encrypt other data, such as a stored private key. One candidate for protecting the user’s key in their own storage. Practitioners caution that losing the passkey then means losing the data.

Inkscape undoes at the level of its XML tree, which makes it directly relevant to XTXP:

  • An action is a transaction. Every change to the document’s XML is recorded as an event (node added, removed or reordered; attribute or content changed). DocumentUndo::done() closes the open transaction and pushes its event log onto the undo stack as one step, with a description for the Undo History dialog.
  • Undo and redo replay logs. Undo reverses a step’s XML events; redo replays them forward.
  • Small steps can merge. DocumentUndo::maybeDone() takes a key. If it matches the previous action’s key, the new events are folded into the top step, so winding a spin button becomes one undo. Drags write to the XML only on release, so a drag is one step too.
  • History is linear. Any new action clears the redo stack: maybeDone() calls clearRedo() before pushing the new step, which is still the case in current Inkscape. Undo a few steps, then edit, and the undone work is gone.
  • Observers can watch commits. An UndoStackObserver is told about each new step, undo and redo.
  • The Undo History dialog nests, but doesn’t branch. Its model (EventLog) shows history as a tree, and its source calls the nesting “branches”. When a new step has the same kind as the previous one, it’s added as a child of that row, so a run of moves collapses under one entry. These are groups of consecutive actions, not alternative histories: _clearRedo() erases every row after the current one, children included.
  • Jumping is replay. Selecting a row in the dialog (seekTo()) calls undo or redo repeatedly until it gets there.
  • History is bounded. The oldest steps expire from the front of the stack, and the dialog then shows “[Changes forgotten]”. A reference point for XTXP’s history limits.

What XTXP takes from it:

Yjs and Yrs have an undo manager that can be scoped to the local user’s own changes: it undoes them by applying inverse operations, and leaves collaborators’ edits alone. That is the model for an individual’s Ctrl+Z in a shared XTXP document, as distinct from branching the document’s history.

Modern shared whiteboards: tldraw, an open-source canvas with multiplayer sync, and Miro. How they handle undo with several people editing, selection, and seeing each other’s work are the questions to check against before settling XTXP’s whiteboard behaviour in Inkscape (the adapter contract).

Branches as cheap forks of history, merges as replays, origin as the authoritative upstream. XTXP’s sessions and the @origin ref borrow all three ideas.

npm and similar tools name a version after an @ (react@18.3.1, pkg@latest). XTXP’s {host}/{path}@{ref} reads the same way.

HTTP calls the server that holds the authoritative version of a resource the origin server, and browsers build their security model around a page’s origin. Hence @origin.

ActivityPub is the W3C protocol behind the fediverse. Mastodon runs it as independent instances, each with its own rules and moderation, that choose whom to federate with. That is the model for how muppet discos might federate, and ActivityPub might even carry it.

Proving control of a domain with a DNS record is well established. ACME’s DNS-01 challenge does it to issue certificates, and Bluesky lets an account claim a domain as its handle with a TXT record that names its identifier. A domain owner claiming @origin by public key works the same way.

Precedents for the use cases:

  • Wikipedia for merge rules layered on open editing: who may edit, how edits are reviewed, how disputes are settled.
  • Everything2 for a different take on who writes what and how it’s kept.
  • MUDs, MUSHes and MOOs, such as LambdaMOO, where users added their own objects, with logic, to a shared text world.
  • WorkAdventure, a shared 2D world for teams, as a candidate for base maps with per-user layers.
  • Google Docs, which defined the shared document editor.

From traditional animation: drawing on translucent paper over the previous frames. XTXP uses the term for drawing a session or pending edits over the current state (Views).

Word processors’ redlines, which show who changed what, before it’s accepted. XTXP’s redline is the same idea, computed from a session’s operations.

  • Hypothesis via proxies any page to add an annotation layer. A working model for overlays done responsibly.
  • Genius Web Annotator overlaid annotations on other people’s sites and drew sustained criticism from site owners about consent. A cautionary tale for live web branching.
  • RFC 5261: XML Patch operations
  • RFC 7351: the application/xml-patch+xml media type
  • RFC 9110: HTTP semantics, including the origin server
  • RFC 5122: xmpp: URIs
  • XEP-0045 (Multi-User Chat), XEP-0198 (Stream Management), XEP-0313 (Message Archive Management), XEP-0352 (Client State Indication), XEP-0357 (Push Notifications), XEP-0308 (Last Message Correction)