Webstrates A research prototype enabling collaborative editing of websites through DOM manipulations.

Webstrates uses the ShareDB backend, persisting and versioning every single change made to a document, down to the keystroke. As a result, a webstrate can be restored to any of its previous versions, either by specifying a version number or a tag.

The operations history can be a accessed through http://<hostname>/<webstrateId>?ops, which is also described in the HTTP API documentation.

Example History

Let’s create a new document, make it contenteditable, and then write “Hello, world!” in the DOM, so we can check out the operation history.

If we start out by navigating to an empty document, say http://<hostname>/my-test-webstrate, we’ll be met with a DOM looking something like:

<html><head><title>my-test-webstrate</title></head><body></body></html>

Inspecting the operations history will shows us something similar to:

{
  "v": 0,
  "create": {
    "type": "http://sharejs.org/types/JSONv0",
    "data": [
      "html", { "__wid": "WeywP48a" },
      [
        "head", { "__wid": "prJTAVWx" },
        [
          "title", { "__wid": "8iqiqAix" },
          "my-test-webstrate"
        ]
      ],
      [
        "body", { "__wid": "mBTnnTpj" }
      ]
    ]
  },
}

What are those __wids? To keep better track of DOM, Webstrates adds unique IDs to each DOM elements. These are usually not visible in the DOM, even though they exist in the JsonML representation. While it should rarely be necessary for users to access the __wid of an element, it is available as id on all non-transient DOM nodes' webstrate objects (i.e. DOMNode.webstrate.id).

If you want to know more about JsonML, check out How it works in the Developer guide.

At this point, we will be at version 0 (evident by the v property on the object). The version can also be accessed by calling version on the global webstrate object:

> webstrate.version
0

Or by accessing http://<hostname>/my-test-webstrate?v:

{ "version": 0 }

After adding contenteditable to the body, so we can write in the document, and adding “Hello, World!” to the document, the document will now look something like:

<html><head><title>my-test-webstrate</title></head><body contenteditable="">Hello, world!</body></html>

And then operations history will have expanded to now include:

[
  ...
  { "v": 1, "op": [ {"oi": "", "p": [3, 1, "contenteditable"] } ] },
  { "v": 2, "op": [ {"li": "H", "p": [ 3, 2 ] } ] },
  { "v": 3, "op": [ {"si": "e", "p": [ 3, 2, 1 ] } ] },
  { "v": 4, "op": [ {"si": "l", "p": [ 3, 2, 2 ] } ] },
  { "v": 5, "op": [ {"si": "l", "p": [ 3, 2, 3 ] } ] },
  { "v": 6, "op": [ {"si": "o", "p": [ 3, 2, 4 ] } ] },
  { "v": 7, "op": [ {"si": ",", "p": [ 3, 2, 5 ] } ] },
  { "v": 8, "op": [ {"si": " ", "p": [ 3, 2, 6 ] } ] },
  { "v": 9, "op": [ {"si": "W", "p": [ 3, 2, 6 ] } ] },
  { "v": 10, "op": [ {"si": "o", "p": [ 3, 2, 8 ] } ] },
  { "v": 11, "op": [ {"si": "r", "p": [ 3, 2, 9 ] } ] },
  { "v": 12, "op": [ {"si": "l", "p": [ 3, 2, 10 ] } ] },
  { "v": 13, "op": [ {"si": "d", "p": [ 3, 2, 11 ] } ] },
  { "v": 14, "op": [ {"si": "!", "p": [ 3, 2, 12 ] } ] }
]

The first operation in the above represents the insertion of the contenteditable attribute (oi stands for object insertion), and the remaining operations each correspond to a key press (li for list insertion and si for string insertion).

As is also evident from the operations history, each key press has been given its own version number. We will now be at version 15:

> webstrate.version
15

Note that we now are at version 15, even though the latest operation is labeled version 14. This is because the version attached to the operation actual indicates the version prior to the operation, just as the first version (the creation) of the document also said version 0.

Restoring a document

Part of the Webstrates philosophy is to document all changes, the operations history being an example of that. As such, reverting a webstrate doesn’t actually delete any operations.

Instead, the differences between the JSON representation of the document at the current version and the desired version is calculated using the json0-ot-diff library which returns the differences as an array of operations. These operations are then applied to the document.

Continuing from the example above, restoring to version 7 – the version right after having finished writing “Hello” – will add just two new operation to the history:

[
  ...
  { "v": 15, "noop": "documentRestore", },
  { "v": 16, "op": [ {"p": [3, 2 ], "ld": "Hello, World!", "li": "Hello" } ] }
]

The first operation being a dummy operation (noop meaning “no operation”) just to indicate that the document was restored.

The second operation here has two parts, first a list deletion (ld) of the previous string (“Hello, World!”), followed by a list insertion (li) of the desired string “Hello”.

After the restoration, the document will now be back to:

<html><head><title>my-test-webstrate</title></head><body contenteditable="">Hello</body></html>

The important thing to note, however, is that the document’s version will not be 7, as we restored to, but rather at 17. We added the two operations created (the noop and the op) to the document history, bringing us from 15 to 17.

Restoring a document is thus non-destructive to the history. Another way of restoring the document could have been to remove all operations after version 7 and rebuilt the document up to that point, but in that case, anything after version 7 would have been lost. Using our approach, it is now possible to “go back to the future”, i.e. restore version 15 again:

> webstrate.version
17
> webstrate.restore(15)
undefined
> webstrate.version
19

Which would again give us a document containing just “Hello, World!”.