The Real-time Editing of Issue Descriptions (REID) SEG is a Single-Engineer Group within our Incubation Engineering Department.
This group is focussed on Real-time collaborative editing of issue descriptions and will build upon the recent work to view real-time updates of assignee in issue sidebar.
The goal is to have real-time collaborative editing of issue descriptions in order to turn issues into a Google Docs type of experience. Initially we will dogfood this at GitLab for meeting agendas and notes.
There are additional ideas within the Real-time collaboration Epic, however the goal of this SEG is the minimal functionality described above.
Make real-time collaboration on GitLab work for everything.
You should never have to leave GitLab for pair programming, collaborating on tech designs and RFC, creating and editing issues, creating and reviewing merge requests, …
GitLabs software stack enables real-time collaboration via Websockets, in particular GraphQL subscriptions. Real-time comes with certain complexities when users want to collaboratively edit text, in particular rich text. In order to allow multiple parties to edit the same text concurrently, we must ensure a conflict-free replication mode between all participating clients. There are several ways to achieve this, but we utilize CRDTs. CRDTs allow us to concurrently edit state from 1, 2, … n
clients and eventually end up with a consistent representation of the document on all clients.
Every participant in the editing process is a client (in comparison to Operational Transformation). The CRDT we picked (YATA) has a popular frontend implementation Y.js and we have created bindings for its Rust port y-crdt
. This allows our Ruby on Rails backend to just act as one more client.
y-rb
gemCurrent State: Published to RubyGems.
This Ruby gem is developed under the y-crdt
umbrella (The y-crdt organization) and brings basic and complex shared types (Array, Map, Text, XML) to Ruby. The encoded updates and state vectors are a 100% compatible with Y.js and therefore state can be synced between the JavaScript frontend and the Ruby backend in a seamless way. This allows us to e.g. add, update, or remove labels via the GitLab UI but also with background jobs (some bot adding a label concurrently).
Current State: Started
GitLab instantiates one or many Apollo clients on its front-end. Clients interact with the server via links. Those links decide which transport protocols are used for certain operations (query, mutation, or subscription). For real-time usecases, we do not only want to receive data via a persistent connection (Websocket), but also want to send changes (mutations) via the same connection. This requires some changes to our Apollo Link setup:
somePrefixOp
via Websocket link)Current State: Not Started
For existing issues, starting a collaborative editing session should be exactly the same as the existing process. In case a second user starts to work on an issue, the backend will create a collaborative editing session between users and make sure that all changes (deltas) are synced between all participating parties.
Current State: Not Started
To determine and replicate a change to the description field, we need to encode everything that a user does as a change event (delta). Y.js uses a binary encoded representation to optimize payloads, but essentially it is a list of operations. One example of how this looks like is the Quill Delta Format.
The GitLab UI does not rely on a uniform text editor but instead offeres a mix of textarea
, tiptap/prosemirror
, Web IDE
, …, multiple editor modes (Raw, WYSISWYG), and post-edit transforms (Markdown parser → references). If we want to opt-in an editor UI into the collaborative editing flow, changes need to be mapped into a replication format accordingly.