<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Collaborative Editing</title>
        <link>https://onp4.com/@vadim/~collaborative-editing</link>
        <description>Collaborative Editing</description>
        <lastBuildDate>Mon, 11 May 2026 11:43:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>P4 - P for posting</generator>
        <language>en</language>
        <copyright>Copyright, P4</copyright>
        <item>
            <link>https://onp4.com/@vadim/p/mxx8yi4jml</link>
            <guid>https://onp4.com/@vadim/p/mxx8yi4jml</guid>
            <pubDate>Tue, 27 May 2025 00:53:06 GMT</pubDate>
            <content:encoded><![CDATA[# Local-first Conf 2025

We are in the middle of the second Local-first Conf (May 26 -- 28), 2025. Just keeping track here of ideas worth a follow up.

## Is IPFS dead?

I personally love CAS (content addressable storage) and IPFS seems like the best distributed transport for CAS we have. I don't see any other better alternatives to CAS for building distributed apps. IMO, CAS is by far the best storage to build distributed apps on top of, better than any other P2P tech I have seen.

However, it looks like the general sentiment at the conference (and overall) is that people have given up on IPFS. It is just too slow, too high latency, too unreliable, and too much of a burden to rebroadcast your hashes all the time...

## Light in the infinite canvas tunnel

As Excalidraw and Tldraw are for-profit corporations now, I was thinking what could be the next open source infinite canvas editor. (I don't have time to create one now.) Fortunately, something is happening in this space. There is now [OCWG](https://canvasprotocol.org/) (*Open Canvas Working Group*), which aims to standardize open canvas storage format, so that there could be one common open source JSON schema capable to represent Figma, Excalidraw, and Tldraw documents. And they even mentioned that they have their own renderer, which renders the OCWG format to an SVG.

## Prolly trees for the resque

Prolly trees in CAS (Content Addressable Storage) are even more useful than I though. First, they give you sorted keys (anyone up for SQL implementation?). But it is even better. Guys from `dialog-db` (a triplet store) mentioned how they use Prolly trees to do a triplet database in CAS. From the root they have three Prolly trees, each tree indexes the different part of the subject-predicate-object triplet. There you go: graph database on top of CAS.

## CRDT deep dive

There might be a new way to do the eg-walker merge on concurrent edits, which does not require CRDTs. [`sdexa`](https://github.com/sdexa) brought up this idea where all you need for the merge of concurrent operations is the offset of the insert, no need for any CRDT IDs. This is not verified yet, it might work, but no working implementation exists yet. `sdexa` looked at this from a very practical perspective: CRDTs are not made for eg-walker merges, so they must carry extra unnecessary information, and that informations turns to be the CRDT character IDs.

I look at this from a bit different perspective, lets think about a common generic way to represent a text insert operation. First lets think how we represent operations in the OT system, it could have three fields:

```js
{ text: 'abc', offset: 5, after: BOF }
```

where, `text` is the inserted text, and `offset` is the position from `after` character. Here after is set to BOF (beginning of file), in OT `after` is always set to BOF, so we normally do not store it in the operation.

Now, lets see how a typical CRDT operation is represented:

```js
{ text: 'abc', offset: 0, after: a.1 }
```

In CRDT it is somewhat in reverse we specify `after` character ID `a.1` (lets say), but the `offset` is always 0. So, in CRDTs we normally do not communicate the `offset` field, the offset after `after`, it is always 0.

But the thing is, actually `offset` is 0 only during the transmission of the operation. Once the operation is *integrated* into the sorted list, if there were concurrent edits, the `offset` might become more than 0. Lets say there was a concurrent edit which inserted also a character after `a.1`, if that edit is selected to appear before my edit, then, once integrated the `offset` of my operation is 1 now, because there is this extract character between my insert and the character `a.1`. __Operation transformations happen in CRDTs implicitly, the integration function is the transformation__.

So, the WOOT (WithOut Operational Transformation) paper was wrong. They still do the transformation, it is just the `offset` field during the operation minting is always 0, so it is never explicitly communicated. And during the integration the `offset` field is potentially incremented, but again, it does not need to be explicitly communicated as the CRDT list order is where that information is stored.

Consider we stored all list RGA (Replicated Growable Array) blocks in a database, each block in a separate SQL row. The table would have these three columns `text`, `offset`, `after`:


| text | offset | after |
| - | - | - |
| X | 0 | a.1 |
| abc | 1 | a.1 |

Let's go back to `sdexa`'s idea. If his idea works out, what it means is that during the eg-walker merge, we don't need the `after` IDs, because during the merge the `offset` contains enough information. During the time collapse, at that specific time, the `offset` might be enough. Oh, and I also shall mention that the case `sdexa` is solving is when there are more than just two branches, he wants to parallelize (run on multiple cores) the eg-walker merge algorithm across branches, as well as being able to arbitrary cut through time.

`sdexa` is packed with knowledge. He showed me how to find cycles in O(log N) in a tree CRDT. Also, he showed a novel way to support moves in a tree CRDT (and enforcing invariants, in general, not just moves) in a way where you don't need to store and order operation history to pick one non-cycle-creating move operation (as is currently state-of-the-art). If his approach works, the moves in tree CRDTs could be supported without needing to store the log of operations at all, one would only need to store one latest move operation. Reject all incoming conflicting moves, if they happen. Then pick one of the move operations. If the incoming one is picked, undo that latest move and apply the incoming move.


## Even more on CRDTs

Going back to the idea of adding [more operations](https://onp4.com/@vadim/p/ofg47urmg9) to a list CRDT. I never really figured out how to add *move* operations, but [Michael's](https://github.com/toomim) *portals* might even allow to do more than just moves, also *copy* and potentially other operations. The place where I was stuck on moves/portals idea for CRDT is I was trying to do them by referencing a range (from this character to that character). But discussing this with Michael, he pointed out that I need to somehow store the "history" or portals (whatever the "history" is), now I think the portals in CRDTs could work, where the history is represented by a reference to the previous portal. So, when you insert a portal into a CRDT, the inserted portal element, instead of being `[startId, endId]` (referencing the start and end of the CRDT sequence, it must be `[startId, startPortalId, endId, endPortalId]`, where `startId` and `endId` are still the start and end characters of the new portal, but `startPortalId` and `endPortalId` are references to the old portals from which those characters were picked. This way, recursively, all the portals can be unwrapped to create the view.

OT has these TP1 and TP2 properties. Michael wants to introduce also TP0, which essentially means it should be possible to construct a diff. Now, a cool thing he presented is that in time collapse (aka eg-walker merge), in a two branch scenario (but you can reduce everything to two branches), all you need for the merge is being able to do a diff between the end state of one branch and the other branch. Really cool stuff! So, again, you don't need a CRDT for the eg-walker time collapse merge.

## RGA *update* operation resurrected

About adding more operations to a list CRDT. When chatting about some deep CRDT internals stuff with [Seph](https://github.com/josephg) and [Alex](https://github.com/alexjg), I remembered that the original RGA (Replicated Growable Array) algorithm also proposed the *update* operation:

![](https://files.onp4.com/og6f0o9v1c/ua3aswcwi0/image.png)

The CRDT implementer community (and, actually, also the research community) largely has completely ignored the *update* operation. All libraries implement only *insert* and *delete* operations. As did `json-joy`, too. But I suddenly realized that *update* might be useful, and not for ordered text, but for ordered array elements.

You see `json-joy` arrays, say `[1, 2, 3]`, also use the RGA algorithm, it is a ordered references-to-other-nodes list. But because only *insert* and *delete* operations are available, it is a common practice to wrap the array elements into another `val` node, which is a LWW register.

![](https://files.onp4.com/og6f0o9v1c/y57q3ktnan/image.png)

What I realized only now, is we would not need that `val` node wrapping, if only `json-joy` implemented the *update* operation. You could update the array element directly, instead of updating the LWW register.

]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/53t15p9aa7</link>
            <guid>https://onp4.com/@vadim/p/53t15p9aa7</guid>
            <pubDate>Wed, 11 Sep 2024 14:58:14 GMT</pubDate>
            <content:encoded><![CDATA[https://mattweidner.com/2024/06/04/server-architectures.html]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/b2sdna4rgj</link>
            <guid>https://onp4.com/@vadim/p/b2sdna4rgj</guid>
            <pubDate>Tue, 20 Aug 2024 12:06:37 GMT</pubDate>
            <content:encoded><![CDATA[*Continuum of trouble*: With no latency, no conflict resolution is necessary. When content is retrievable and within 50ms, it is stored *locally*. All other cases are further down the trouble continuum, and are called *remote* storage.]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/t6vu7pm4r3</link>
            <guid>https://onp4.com/@vadim/p/t6vu7pm4r3</guid>
            <pubDate>Wed, 13 Mar 2024 22:44:51 GMT</pubDate>
            <content:encoded><![CDATA["frontier of causal stability"]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/ofg47urmg9</link>
            <guid>https://onp4.com/@vadim/p/ofg47urmg9</guid>
            <pubDate>Tue, 13 Feb 2024 10:32:45 GMT</pubDate>
            <content:encoded><![CDATA[What about adding more operations to text CRDT, besides the usual *insert* and *delete*?]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/wzfncd3yjk</link>
            <guid>https://onp4.com/@vadim/p/wzfncd3yjk</guid>
            <pubDate>Tue, 13 Feb 2024 08:54:03 GMT</pubDate>
            <content:encoded><![CDATA[Some nice OT extension presentation "Operational Version Control" from Jonathan (starts around 30min):

https://braid.org/meeting-78]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/scjty6oj2l</link>
            <guid>https://onp4.com/@vadim/p/scjty6oj2l</guid>
            <pubDate>Wed, 06 Sep 2023 08:42:34 GMT</pubDate>
            <content:encoded><![CDATA[https://madebyevan.com/algos/]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/85zodnsaoj</link>
            <guid>https://onp4.com/@vadim/p/85zodnsaoj</guid>
            <pubDate>Thu, 31 Aug 2023 08:18:17 GMT</pubDate>
            <content:encoded><![CDATA[https://github.com/cn-uofbasel/ssb-publications]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/9lyxctf6eo</link>
            <guid>https://onp4.com/@vadim/p/9lyxctf6eo</guid>
            <pubDate>Sun, 27 Aug 2023 14:58:12 GMT</pubDate>
            <content:encoded><![CDATA[https://vlcn.io/]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/089f2twvxf</link>
            <guid>https://onp4.com/@vadim/p/089f2twvxf</guid>
            <pubDate>Sun, 27 Aug 2023 13:04:49 GMT</pubDate>
            <content:encoded><![CDATA[# JSON CRDT 2.0

Things to consider for the next version of JSON CRDT. List of ideas that need to be polished for the next JSON CRDT iteration:

- Faster RGA implementation, one optimisation is to reference RGA block ID in insert operation. This will speed up insertion from ~O(log N) to ~O(1), but at a cost of extra ID passed with each insert operation and the internal data structure of the RGA will need to index each block by its initial ID.
- Move operations across different document nodes. Consider moves within an object vs moves across the whole document.
- Low-level multi-value register support. (MV register can be done in user-space using an RGA array.)
- Currently, JSON CRDT is operation-based CRDT. Consider if it also should work as state-based CRDT and delta CRDT.
  - Store metadata necessary to re-create patches in the document, so that patches don't need to be stored separately.
- Recursion and duplicate nodes.
  - In principle a CRDT can function where some of the nodes are used/referenced more than once, even if the references create a recursion in node structure. In practice, does that have a use case?
- Serialisation for [CAS](https://en.wikipedia.org/wiki/Content-addressable_storage)
  - Stable serialisation, e.g. same document or same user changes result in the same serialised representation, which allows for de-duplication.
  - [Serialisation that works well with CDC](https://github.com/streamich/json-joy/issues/229).
  - Consider if there is a good scenario where JSON CRDT 2.0 could support infinite size JSON-like CRDT based on CAS, where nodes can be referenced by their CIDs. (CAS ensures there are no recursive loops.)
- Currently JSON CRDT state binary encoding is based on MessagePack, maybe redo it on top of CBOR.
  - CBOR is slightly slower than MessagePack, but that will allow to bundle only CBOR dependencies, for example, in IPFS-based application, as IPFS uses CBOR for DAG serialisation.
- Low barrier to entry
  - Consider read-only clients, that only want to read the document. There could be a serialisation method which is easy for them to parse.
  - Consider JSON CRDT clients that should function but cannot support Block-wise RGA algorithm. I.e. all RGA data types would be read-only in those clients, but LWW data types would still function as normal.
- Rich-text support considerations.
  - Storing node IDs in user-space, for example, Peritext block and inline annotations storage in user-space RGA array. The main semantic problem is that now user-space stores IDs, which are text RGA internals, and those IDs cannot be used to render rich text, without parsing the RGA internals.
  - Consider if it is possible to have nested inline text annotations. For example, `<sub>` inside of `<sup>`. That way arbitrary complexity math formulas should be possible to edit.
- Algorithm support: JSON CRDT supports just two algorithms RGA and Last-write wins registers. Out of those more complex data types can be built, for example, out of RGA a sets and multi-value registers can be constructed. Consider if it is necessary to introduce more/other algorithms into the implementation.
- Reconsider clocks
  - JSON CRDT supports two clock types: (1) Logical vector clock; (2) Total Order Broadcast server clock.
  - For JSON CRDT 2.0 consider if any of the following clocks could be used: (1) Merkle clock; (2) bounded version vectors; (3) Hybrid logical clock; (3) Dotted version vectors; (4) Tree clocks; (5) Interval tree clocks; (6) GUN-like wall clock with constraints.
- Merkle DAG CRDT native support. Maybe embrace Merle DAG:
  - Clocks would be Merkle clocks.
  - All objects could reference each other by CIDs.
  - There could be "infinite" length CRDTs, such as HAMT CRDT and Feed CRDT in P4. Infinite-length data type support:
    - HAMT CRDT --- an infinite size key-value LWW CRDT.
    - Feed CRDT --- an infinite size ordered list.
    - Maybe group CRDTs into: (1) finite size; (2) unbound size ones.
  - For performance, could be still useful to group some objects into blocks: say, RGA objects are their separate blocks but LWW objects could be grouped in one DAG block.
  - Consider how to work around IPFS block size constraints for large CRDTs.
  - Adding additional pointers to the DAG to work around thin DAH problem.
- Natural evolution to v2. Maybe, instead of the stop-the-world-full-rewrite the JSON CRDT could seamlessly migrate to JSON CRDT 2.0, e.g. JSON CRDT 2.0 would be a superset of JSON CRDT.
- Consider embedding "stored procedures" inside the CRDT or having an ability for operations to be custom defined by some scripting language. For example, use the [JSON Expression](https://github.com/streamich/json-joy/blob/master/src/json-expression/README.md) language.
- Consider adding Reference Value type, which is a value which refers to another CRDT.
- GraphQL-like API, IPLD-like lenses, "projectsion API", ability to subscribe to a subset of data, especially if CRDT is a graph of nodes in CAS.
- Research why YATA algorithm generates less chunks than Blockwise-RGA. (At least that is the case using Martin's trace.)
- Rich-text/Peritext integration into the main spec?
- Code editor support---they require quick navigation by line number. Maybe Peritext could insert virtual block at every newline `\n` position and provide fast line indexing.
- Dealing with loss of context (large deletions)---imagine in a collaborative rich-text editor user selects few paragraphs (maybe whole document) to copy the text `Ctrl + C`, but accidentally presses `Ctrl + X`, instead, then---to fix the document---user re-inserts the text with `Ctrl + V`. This results in complete loss of content and all causal context for other users (other user cursors get lost, other user inserts get inserted into a void).
- Optimization: local RGA inserts do not need to follow the standard RGA algorithm. Local inserts do not encounter *preemptive siblings*, i.e. when doing a local insert, there should be no IDs between the `ref` ID and the new op ID.
- RGA is composed of two binary trees. It is serialized by text-order. Consider ability to serialize by ID-order. This way on the server the RGA could be unserialized with balanced ID tree and insertion could be implemented without hydrating the text-order tree. Even if text-order tree is hydrated, the main advantaged is to have the ID-order tree balanced, as that tree will be used to perform an insertion lookup.
- When unserializing balance the by-ID tree, instead the by-text tree.
- Are there better algorithms then RGA? What about YATA?
- Consider explicitly support deep integration with UI to be able to perform local operations at `O(1)` speed. For example, a cursor in a text editor can maintain a direct pointer to the RGA chunk to which the cursor is pointing.
- Use real world traces to compute relative frequencies of different insert/delete code paths. (Inset at root, insert at root with concurrency, insert after a block, insert after a block with merge, block split, deletes ...).
- When doing splay rotations, consider moving tobstones as low as possible in zig-zig rotation.
- Consider implementing triple-zip rotations, where tombsones are extra rotated down.
- Consider storing the whole causal graph, i.e. each operation would also contain the "parent"---the last know operation in the document or the last known operation in a specific object.
- Maybe Peritext can be integrated somehow into JSON CRDT such that internal timestamps are not exposed. Maybe using some format like Quill Delta.
- Switch to Fugue algorithm? Or, execute RGA by default and add ability to optionally switch into Fugue-mode, when the right origin is presented in the operation.
- Sync9 and Fugue result into the same sorting order, because, apparently, to solve for interleaving there is not much possibilities for alternative sorting variants. Maybe, it is possible to solve for all interleaving problems without much extra metadata (with less metadata than proposed in Fugue)?
- *Permanent Range Deletes* --- RGA delete operation where: (1) a range is deleted; (2) tombstones are deleted forever, with exception for the last character in the deletion range, which is left as a tombstone.]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/16xlv8ayq1</link>
            <guid>https://onp4.com/@vadim/p/16xlv8ayq1</guid>
            <pubDate>Sun, 25 Jun 2023 19:39:40 GMT</pubDate>
            <content:encoded><![CDATA[AI generating nonsense]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/jy4shqsrau</link>
            <guid>https://onp4.com/@vadim/p/jy4shqsrau</guid>
            <pubDate>Fri, 19 May 2023 22:37:38 GMT</pubDate>
            <content:encoded><![CDATA[https://github.com/vlcn-io/cr-sqlite]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/5z4zj1qdoe</link>
            <guid>https://onp4.com/@vadim/p/5z4zj1qdoe</guid>
            <pubDate>Sun, 23 Apr 2023 19:26:26 GMT</pubDate>
            <content:encoded><![CDATA[https://christophermeiklejohn.com/crdt/2014/07/22/readings-in-crdts.html]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/iyc64k1k7h</link>
            <guid>https://onp4.com/@vadim/p/iyc64k1k7h</guid>
            <pubDate>Thu, 13 Apr 2023 17:16:18 GMT</pubDate>
            <content:encoded><![CDATA[Compare and swap --- or *optimistic transactions* on per-register granularity. Two options:

1. Operation contains the previous value of a register and succeeds only if that value matches the latest value in the central database.
2. Each register has a sequence number, operations succeeds only if the previous sequence number is still the latest value for that register.]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/uwue84oxwn</link>
            <guid>https://onp4.com/@vadim/p/uwue84oxwn</guid>
            <pubDate>Thu, 13 Apr 2023 17:00:31 GMT</pubDate>
            <content:encoded><![CDATA[https://irisate.com/]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/wi207kyue0</link>
            <guid>https://onp4.com/@vadim/p/wi207kyue0</guid>
            <pubDate>Mon, 10 Apr 2023 12:52:42 GMT</pubDate>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/rdge4odxp1</link>
            <guid>https://onp4.com/@vadim/p/rdge4odxp1</guid>
            <pubDate>Mon, 10 Apr 2023 12:52:27 GMT</pubDate>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/x2p2f1qilq</link>
            <guid>https://onp4.com/@vadim/p/x2p2f1qilq</guid>
            <pubDate>Mon, 10 Apr 2023 12:52:16 GMT</pubDate>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/ff7hbx91oi</link>
            <guid>https://onp4.com/@vadim/p/ff7hbx91oi</guid>
            <pubDate>Mon, 10 Apr 2023 12:51:58 GMT</pubDate>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/1uili1bbww</link>
            <guid>https://onp4.com/@vadim/p/1uili1bbww</guid>
            <pubDate>Mon, 10 Apr 2023 12:51:50 GMT</pubDate>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/jxij2td6yb</link>
            <guid>https://onp4.com/@vadim/p/jxij2td6yb</guid>
            <pubDate>Wed, 05 Apr 2023 18:31:07 GMT</pubDate>
            <content:encoded><![CDATA[https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.627.5286&rep=rep1&type=pdf]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/ykxv9fjbib</link>
            <guid>https://onp4.com/@vadim/p/ykxv9fjbib</guid>
            <pubDate>Mon, 27 Mar 2023 22:41:47 GMT</pubDate>
            <content:encoded><![CDATA[https://github.com/gritzko/citrea-model/blob/master/story.md]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/7uod7b3tf2</link>
            <guid>https://onp4.com/@vadim/p/7uod7b3tf2</guid>
            <pubDate>Mon, 27 Mar 2023 22:41:24 GMT</pubDate>
            <content:encoded><![CDATA[http://archagon.net/blog/2018/03/24/data-laced-with-history/]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/m8f4gpp4jp</link>
            <guid>https://onp4.com/@vadim/p/m8f4gpp4jp</guid>
            <pubDate>Tue, 20 Dec 2022 21:08:42 GMT</pubDate>
            <content:encoded><![CDATA["Causal length"]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/nmb9fwx8ce</link>
            <guid>https://onp4.com/@vadim/p/nmb9fwx8ce</guid>
            <pubDate>Tue, 20 Dec 2022 21:07:51 GMT</pubDate>
            <content:encoded><![CDATA[Starting from 50min, Matt shows how to extend an SQL table so that every row and every cell is a LWW register, that creates a distributed SQL table (no central server, say an SQLite table):

https://braid.org/meeting-40]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@aleks/p/m46f53dvsu</link>
            <guid>https://onp4.com/@aleks/p/m46f53dvsu</guid>
            <pubDate>Mon, 19 Dec 2022 20:52:30 GMT</pubDate>
            <content:encoded><![CDATA[https://martin.kleppmann.com/papers/convergence-cacm.pdf]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/2cfgo3gdpo</link>
            <guid>https://onp4.com/@vadim/p/2cfgo3gdpo</guid>
            <pubDate>Sat, 03 Dec 2022 00:18:18 GMT</pubDate>
            <content:encoded><![CDATA[Google Docs collaborative editing using OT in [part 1][part-1], [part 2][part-2], and [part 3][part-3].

[part-1]: https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs_21.html
[part-2]: https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs_22.html
[part-3]: https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs.html]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/ux2dyr78ed</link>
            <guid>https://onp4.com/@vadim/p/ux2dyr78ed</guid>
            <pubDate>Fri, 02 Dec 2022 23:58:59 GMT</pubDate>
            <content:encoded><![CDATA[PaPoC conference programs:

- [PaPoC 2022](https://papoc-workshop.github.io/2022/program.html)
- [PaPoC 2021](https://papoc-workshop.github.io/2021/program.html)
- [PaPoC 2020](https://papoc-workshop.github.io/2020/programme.html)
- [PaPoC 2019](https://papoc-workshop.github.io/2019/program.html)
- [PaPoC 2018](https://papoc-workshop.github.io/2018/program.html)
- [PaPoC 2017](https://software.imdea.org/Conferences/PAPOC17/program.shtml)
]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/el4t4hgfqr</link>
            <guid>https://onp4.com/@vadim/p/el4t4hgfqr</guid>
            <pubDate>Fri, 02 Dec 2022 22:33:17 GMT</pubDate>
            <content:encoded><![CDATA[Ritzy implements its own CRDT and editor layout surface. Interestingly the layout surface does not use local operations, instead cursor position and operations are only in remote form.

https://github.com/ritzyed/ritzy/blob/master/docs/DESIGN.adoc]]></content:encoded>
        </item>
        <item>
            <link>https://onp4.com/@vadim/p/y3zwtmx9as</link>
            <guid>https://onp4.com/@vadim/p/y3zwtmx9as</guid>
            <pubDate>Wed, 30 Nov 2022 22:57:03 GMT</pubDate>
            <content:encoded><![CDATA[https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs.html]]></content:encoded>
        </item>
    </channel>
</rss>