 I'm looking at a slide counter that says slide two of 177, so I'm just gonna get started. Hey everyone, hope your day has been going well so far. You've seen some cool talks and learned some new and exciting things. Thanks for coming by and I'll do my best not to keep you from the happy hour. I'm Justin Weiss and I'm on the engineering team at AHA, which is the world's number one product road map company. We're a distributed company and we're hiring remote devs all across North America. I also occasionally write from my site, JustinWeiss.com, and I wrote a book practicing Rails to help developers learn Rails without getting overwhelmed. Now, I've been using Rails for over a decade and one of my favorite things about Rails is just how great it is at solving the problems of like the 99% of websites. Take some information, do some stuff with it, show it to some other users. But eventually, hopefully, your site becomes super popular. You start taking lots of information from lots of people and sometimes you start taking lots of information from lots of people into the same place and at the same time. And this can be disastrous. I mean, have you ever written a beautiful multi-page bug description, thrown some photos in there, maybe a short screencast? And then you have this coworker who went out to lunch. They came back, they noticed a typo, and they helpfully destroyed your entire morning's work. Or had a bunch of smart people who were all trying to put flood information into the same table and are constantly overriding each other's changes. Now, wouldn't it be great if everybody could just work on a document at the same time? Like if your text editor could deal with all of those updates, instead of you having to coordinate on Slack like a bunch of stranded pre-teens from Lord of the Flies. As more and more companies and more and more people will put all of their documents online, you start to be able to expect to work on them together. Google Docs is probably the first example that would come to your mind. But there are new apps like Quip and Coda that are building entire businesses around this idea of collaborative editing. And other text editors are adding it, like Sublime, Atom, even Confluence. And if you don't wanna have to pass around the editing football just to get work done, you need it in your app too. What does two developers working on the same document look like? Since you're developers, you'll probably think of Git. You know, you make changes on your document, somebody else makes changes on their document, you merge in one of you fixes complex. Now, part of that's great. And you can just make changes. You don't have to check in and check out the file or make sure that the other person's done with it before you can start editing it. That's called making changes optimistically. And I can do stuff without having to tell you about it first. But part of that's not great. If you and I are editing the same document at the same time, we don't wanna be interrupted every few minutes to deal with conflicts. Like that would just be unusable. But conflicts don't constantly happen. They only happen when we make changes that interfere with each other, which isn't super common. When a conflict does happen though, what if we weren't bugged about it? Like what if the document could just make its best guess what to do and one of us could come and fix it if it guessed wrong? In theory, this is a terrible idea that would never work. But in practice, it mostly does. And that's because the system doesn't need to be right. It just needs to be consistent and it needs to try to keep your intent. Here's what that means. If I type the word hello into a document, the system should do its very best to make sure that hello ends up in the final state of that document. That's what I mean when I say intent. And if somebody else types buy into the same spot at the same time, the two documents should eventually end up exactly the same whether that's hello buy or buy hello. They need to be consistent at the end like you can see here. What about conflicts? Well people are kind of natural conflict resolving machines. Like if you're walking down the hallway and somebody's about to walk into you, you'll stop. And you'll probably both move over to the same side. And then if you're unlucky, you'll both move back over to the other side. But eventually, like you'll look, you'll laugh and one of you will stay still and the other one will go and the conflict is resolved. So you want your editor to quickly respond to the person using it. Like if I make a change, I don't wanna have to wait for a round trip to the server before I can see the result of my change. And you eventually want the other people to be able to see that change that you made, at least as best they can. And you wanna be able to do this as quickly as possible. So how can you make that work? The first thing you might think of is sending diffs. Like Jane changed line five from this to this. But it's kind of hard to see intent in a diff. All a diff really does is it tells you what changed, not why. A better way would be to think in terms of actions that a person could take. Like I inserted character A at position five, I deleted character B at position eight. You can make these actions or these operations pretty much whatever you want. Everything from insert some text to make this section of text bold. You can apply these operations to a document and when you do, the document changes. So applying this operation up here would change that document from hello to hello world. If we have operations and we can send operations around and we can change documents by applying operations, you almost already have collaboration. Like what if client A sent its insert world at five operation to client B? Client B could apply that operation and you'd end up at the same document. Bingo job's finished, it's perfect except it isn't. And that's because you can both be changing the document at the same time. So let's say you have two clients. They each have a document with that text at. Now the left client types the letter C at position zero making the word cat. At the same time, the other client types are at position one making the word art. Now the client on the right gets the insert C at zero from the other client and ends up with cart. Like so far so good. But then the client on the left gets the insert R at one operation from the other client and ends up with crat. And I have no idea what a crat is. But worse than that, we violated one of our most important rules already. Our two documents need to be consistent with each other. They need to end up the same. Because now if the client on the left deletes the character A, it's actually deleting the character R on the other document. Like that's wrong and it's broken and it's confusing in a way that a person can't fix anymore. So something else needs to happen. And that something is operational transformation. Now they call it that because you take operations and you transform them. So let's look at the problem again. We have two documents where operations happen at the same time. And when I say at the same time, I mean that they both happened on the same state of the document, that at one. Whenever we have two documents that happen on the exact same document that means we might need to change one of them. And that's because these operations can affect each other. Like the order that you run these operations in matters. On this client, that means that insert R has to change so that it can happen after the insert C happens. So what would that look like? Well after you insert C, the old position one up there, the one in yellow, shifts over by one spot. It becomes position two. Everything moves over by one. If the R should go in between the A and the T, just like it did over on the other side, it needs to go into position two now, right? So when you get that insert R at one, you have to change it to insert R at two before you can apply it. How about on the other side? Well, it gets the insert C at zero, but the position zero hasn't changed at all. So it doesn't need to do anything. It can just insert that right in there and it all works. So this is an example of why you might need to change an operation. But what are you actually trying to accomplish by transforming it? Or really what you're trying to do is say, if operation A and operation B happen at the same time, how could I change operation B so that it takes into account the effects of operation A before I run it so that they can run it one after another instead of at the same time? This can sometimes be abstract, especially as you end up with more complicated operations and it can be hard to think about. So I draw boxes instead. I have lots and lots of little pieces of paper spread around my desk filled with little boxes. But check this out. In the upper left corner, I would write a document state at. I draw an arrow going right and write one of the operations, insert C at zero on it and then write the result of that operation in the top right. And then I draw a line going down. Next, I draw an arrow going down on the other side and this one gets the other operation, insert R at one. Same as before, I write down what things would look like after you run that operation, so art. And then I draw an arrow going right. And now you have a decision to make because in that lower right hand corner, what should the document look like after both of these operations have happened? Sometimes this is obvious. Sometimes you have to think about what your user would expect having run both those operations and sometimes you just need to make a decision and go with it. In this case though, the answer isn't really ambiguous, it should be cart. And so we'll fill in those two blank arrows to get to that point. What would need to happen to turn art into cart? You need to insert C at zero. What would need to happen to turn cat into cart? Insert R at two. And those two arrows, your right arrow and your bottom arrow, those are your two transformed operations. Once you know those, it becomes really easy to test and TDD this stuff because you have your actual, which are the operations that you happen at the same time, and you have your expected, which are the ones on the right arrow and the bottom arrow. And now you know what to expect and you can write the code that will generate those for you. But before you could actually write a transformation function, there's one last thing that you need to answer. And that's how do you break ties? Like if both clients are trying to insert the same text into the same position, one of them needs to go before and one of them needs to go after and that needs to happen consistently. So pick a consistent tiebreaker. If you're using a client server communication, you can just decide that the server always wins. Or if you're doing client to client or you just want to do it this way, you can give every client a random client ID and just say that the biggest client ID always wins. So we have some operations that transform and some return values that we expect to get back. What would this transform function actually look like? We can start with this. This transforms the top side against the left side to get the bottom and then it transforms the left against the top to get the right and that completes our square. But that's also just kind of punting the question a little bit down the road and probably not what you're actually interested in seeing. A better question is, what would one of these transform operation functions look like? Well, let's focus on this one part. How do you take the left operation and have it take into account the effects of the top operation so that it becomes the right operation? How do you get that right arrow from that left and that top? If we're only thinking about inserting text for now, it's actually not too hard. We'll write it to you to handle it the rest later. Next, we'll return a new operation because we don't wanna mess anything up by changing the one that's passed to us. Like somebody could be storing that for something else later and we don't wanna mess with it just for the purposes of transforming it. Now we need to think, what might cause our position to change? Well, if somebody is inserting text before our position, that means that our position needs to change. Or if they're inserting it at the same position as us and we lose the tiebreaker, then we also need to change. And then how much do we need to change by? Well, the length of the text that was inserted. Like if we put one character before us, we need to move over by one. If we put two, we need to move over by two. And this is exactly what we saw before because somebody typed a C before our operation. We need to move our operation over by one position. And that's it. This is probably about as simple as transformation functions get, but most of them still follow the same kind of pattern of figure out if the other operation can affect ours somehow and if it can take into account the effect of that operation in the one that we return. These can get complicated, but transformations are really functional from the mathematical sense. Like you give them the same inputs, they'll give you back the same outputs. And this makes it really easy to unit test them. And there are some other properties that transformation functions have to fulfill. For example, there's a property that says if you have two documents at the same state and you apply the first operation and then the transformed second operation, you end up at the same state as if you apply the second operation and the transformed first operation, which is kind of a mouthful. But all it really says is with one of these squares, if you start in the upper left-hand corner and you take the top arrow and then the right arrow, you end up in the same spot as if you took the left arrow and the bottom arrow. No matter which way you'll go around that square, you end up in the same place. And that's exactly what we saw here. It's really nice having math because it makes it even easier to test your transformation functions. Like you can generate a whole bunch of random operations. You can transform them in all kinds of different ways as long as they fulfill that property and then you can apply them into documents. And as long as the documents are equal when you compare them at the end, that means that those transformation functions worked properly. So even though these transformation functions can get complicated and when you look at them, it can be sometimes difficult to understand that they're doing the right thing, it's really easy to verify that they're doing the right thing. Now these squared diagrams, they really only work if there are two clients sending operations at the same time. Like there's only really two paths from that top left to the bottom right. If you have three clients, you end up with three dimensional diagrams. If you have four, they have become four dimensional diagrams and so on. And every path through one of these diagrams has to end up at the same place. But if you have a single source of truth, like a server or a database, this all becomes so much easier because instead of a three dimensional diagram, you just have a bunch of two dimensional diagrams. One for each client server connection. The clients don't talk to each other directly, they talk through the server and the server broker's order between the cluster of clients. In Rails, we're fairly used to dealing with backend services like we're probably not writing a Rails app that doesn't depend on a database but supports collaborative editing. And so from now on, I'm going to assume that we're using a server and speak in terms of that. Now we just talked about transformation functions. These functions, they transform operations so that you can have one operation taken to affect the effect of another operation. But there's one other piece that you need. We still need to know which operations to transform, like when we need to transform operations. And to figure that out, the control algorithm needs to know if two documents are the same so that we can tell if two operations happen at the same time. Because remember that's what we mean by happen at the same time, they came from the same document state. But since we're talking to a server, this is really easy. We can just give each state of the document a unique version number and we can say two operations are the same if they came from the same version of the document. Once we have a version on the document, we can also put a version on each operation. And then it becomes really easy to know when two operations happen at the same time. You just compare versions and you say, okay, there's a collision here and we need to do some transformation. But what would happen if you went a little bit further before you sunk up? Like what if each client ran two operations before they collaborated together instead of just one? This is gonna get a little complicated so first have this cat. Once again, it's easier if you draw a square so you can really see what's happening. Just like before, we have some arrows on the top and some arrows on the left. And you wanna complete that square by getting the arrows on the right and the arrows on the bottom. You can use the same transformation functions that we already wrote to do each of these steps. But there's a little bit of a trick here because this isn't just one square, it's actually four. And this is really important because the right side of one square becomes the new left side of the next square. Like the end of one stage is the beginning of the next stage. You have to carry this stuff over. Now this is the most brain bending part of the talk so don't worry if it doesn't really sink in at first. It actually took people years to figure out that this was a problem and how to solve it. Just remember that you need to fill in every one of those arrows. That you can only have one operation on each arrow and that for each one of those squares if you take the top arrow and the right arrow you need to end up at the same state as if you took the left arrow and the bottom arrow. So your control algorithm, the thing that would transform these groups of operations, looks a little bit like this. You have your two lists of operations, your top list and your left list. And two empty lists, the right list and the bottom list. You transform top and left to get your transformed left and your bottom and then you put the bottom operation onto your bottom list. The transformed left, you gotta hold on to for this next step because now you're going to transform the top, the next top operation against that one you were holding on to. You take the bottom result of that and push that onto the bottom list and then you do the same thing with the right operation. If you have more, you hold on to it for the next step. If you don't, that becomes your new right operation. You push that onto your right list and you move on to the next row. So now we have one element in our right list and a full row of bottom operations. So for the next row, our bottom list becomes the new top list and we just keep doing the same thing. Again, this part is by far the most complicated thing you'll see in the entire talk. So don't worry if it doesn't really sink in at first. And it might also help to see it in code. So the first thing we will do is make sure we're only dealing with arrays because it just makes some of our life simpler for the rest of this function and for the rest of our control algorithm we'll only be thinking about arrays. Next, we'll handle some simple cases. Like if either our left list or our top list is empty, there's nothing to transform. That's like I walked away from my desk, you did a bunch of stuff, there's nothing that's going to interfere with that. If you're only transforming one operation against one other operation, this is the exact same function that we saw earlier where you have a single square and you're just trying to get the right operation, the bottom operation off of that. Now for the tricky part. We have multiple operations in a row to deal with. So first we'll create our empty lists that will be our return values. Next, if you remember, we'll work on one row at a time. So for the first row, we go through each operation on the top in our inner loop. We transform those by calling this function recursively. This is almost always going to hit one of those two easy cases, so don't think too hard about that. Just say, I have a transform function, it's going to give me the right and bottom. It's fine. This is going to return those new transformed operations which we'll hold on to. We get back our right operation and remember we use that as the left the next time through this loop. So we'll set that here. And we'll take the bottom operation we got back and we'll add it to the bottom list. Once we're done with the whole row, the last operation that we get is the one that we'll end up with. So we'll add it to our right list. This says left up up here, but if you look up, left up and right up are equal at this point and it's a little bit easier to get a hold of left up at this point. All right, or yeah, left up at this point. And then for the next time through our loop, the bottom list becomes the new top list and we just keep going through until we reach the end. And then when we're all finished with this, we return the right and the bottom list back to the user and we have fully transformed that two by two state that we saw. So, piece of cake, right? Now one plus side about this is that you're probably never going to have to write a function like this yourself and the reason why is because nothing in that function was specific to any kind of app. It spoke in terms of operations but didn't care at all about what those operations actually did. All I was talking about is when do I need to transform them and how do I need to transform them? How do I need to call these functions to transform them? So that leaves the question of what should your operations actually do? Like what should your operations be? And the answer is anything your app wants. Like, as long as you can write transformation functions that fulfill those properties for them, you can invent new operations all day. And this is great because with more specific operations you can get closer and closer to representing what a user is actually doing with your app. But like everything, there's a trade-off because if you have more detailed operations, you tend to have more operations and if you have more operations, you end up needing to write a whole lot more transformation functions. When I was working on this problem, I had 13 different operations which doesn't seem like a whole lot but it meant that I had to write over 100 different transformation functions to deal with them all. I could rely on a whole lot of helpers and things like that even so, like that's a lot of work. But it did mean that we could keep some really strong user intent when they were modifying the document. Like, we could know exactly what they were trying to do and make sure that we were trying to preserve that as best as we could. And that brings us to what kind of decisions make collaboration easier and which decisions, if you make them, are going to make collaboration nearly impossible to build into your app. The first thing is, if you're planning to transform operations, you have to speak in terms of operations. Like, you have to talk about what actions a person is taking, not what state the document is in. If you're only storing full document states, you might have a tough road ahead of you. There are ways around this, like sometimes you can look at diffs of your document and infer actions from that, which is a good worst case scenario, but like I said at the beginning, you lose a lot of intent that way and there are some operations that you can't really represent in that way. So first, think in terms of operations. Like, think insert T at position zero, not the document changed from A to at. You can take this even further. Like, there's a text editor out there called Quill that actually represents documents as just lists of operations. So like, a document would be insert hello, switch to bold, insert world, just a list of actions. Number two, if you can, keep your document state, like what actually is a document, keep it linear. By that I mean it's awesome if you can treat your document as like an array of characters or an array of objects or whatever. It's really easy to transform array indexes and transformation functions. It's a lot harder if your stake gets a little bit more complicated. You can sometimes represent trees linearly too. Like if you have items in your array that mean enter subtree or exit subtree or add formatting, remove formatting. This is still easy to transform but it's a little bit harder to work with when you're actually working with your document. If you have to transform trees, it's not the worst thing in the world. Instead of using indexes, you can use arrays of indexes. Like this node could be reached by one comma one, child one of child one. When I was working on this, trees actually made it much easier to model the kinds of documents that I wanted. And so this turned out to be the best way to handle it even though it made transformation a little bit harder. Finally, keep your actual data as transformable or as mergeable as possible. Like strings are pretty easy to transform. Numbers are pretty easy to merge like you add them or whatever. If you have conflicts on like a custom object, then you have a lot tougher decisions to make and it becomes a lot harder to make sure that you're keeping that user intent and keeping those things together. So taking a step back, what does all of this look like from a higher level? How does this all fit together? So we have document states. We'll call them arrays of characters to keep things simple. And documents also have a version. Each client and the server each have a copy of the document at a specific version. You apply an operation on your own document right away and then you also send that to the server so that the server can send it to other clients and they can see what you did. Sometimes the server will say, hey, that's cool. I haven't seen any new operations yet. Like my version that I have is the same as your version. It'll acknowledge your version, you update your version and now you're back in sync together. You're just basically back at that original state. Other times it'll say, no, I can't actually take that version because I've already seen something that's a newer version. But here are all of the operations between that version and yours so you can catch up. When that happens, you transform their operations against yours because yours has already happened and so like you can't really reorder them and you apply those transformed operations to your document. Then you do the opposite. You transform your operation against all of theirs because from the server's perspective, yours hasn't happened yet, but all of these have and then you send your transformed one to the server. Hopefully this time the server will accept it and once it does, everything is consistent again and we're back up to that original state. So with these, we can build an editor that can handle multiple people editing it at the same time but that's not quite enough to have a really great collaborative experience and I'll show you two reasons why. First, if you have two people editing the same document at the same time, it looks like these letters and words are just kind of appearing out of nowhere. You have no idea where to expect them or what to expect until it happens. So it'd be nice if you could see the cursors of other people editing your documents so you could see who else is there and what they're doing. Second, let's say you make a mistake and you hit undo. You now have a question to answer. Should you undo the last thing that you did or the last change that anybody made? We'll call the one where you only undo your own changes, local undo and the one where you can change undo, anybody's changes, global undo. And if you try editors with both of these different styles of undo, it becomes pretty obvious pretty quickly that local undo is the one that feels natural. Like if I type in a character, I undo should undo that regardless of what anybody else has done in the meantime. So to have a great collaborative editor, we need to add cursor reporting and local undo. We'll start with cursor synchronizing. Now what is a cursor, really? Oh, if we're treating our document as an array of things, you can think of a cursor as a place in that array. Like if I have a document, hello, and my cursor is at before the E, you can say my cursor is at position one. If it's after the O, you can say that it's at position five. So your cursor is just an index into that array. How about other people's cursors? These cursors can also be numbers, but you probably also want to know who's is whose so you can show like a name or something. So you can attach some kind of identifier to them, we'll just call that a client ID. So you have two numbers for remote cursors, a position and a client ID. This makes our document a little bit more complicated, but it's not too bad. You have our array of things, a version, our own cursor, and a list of remote cursors. And with this, when you render your document, you have enough information that you could actually render all those cursors however you want, like just put like a trans player and offset on the page or something like that. But what would happen when you typed in a character or you deleted a character? Well, going back to our document cart, let's say we're looking at our screen and client two left the cursor at position two, so in between A and R. And then when we run the operation insert H at position one, now our document says chart. And where should it draw a client two's cursor? Well, it would make the most sense to keep it where it was before, so in between the A and the R. And so we can pretend that place cursor in this position is an operation and we can transform it against the action that we just took. That looks like this. We inserted a character before the cursor and so we needed to move it over by one. So anytime you perform an operation, you need to transform all of the cursors that you know about against that operation to keep them in the same place. These transformations tend to be really easy. They're almost exactly the same as the insert text because in both of them you're doing the same kind of thing, you're just moving a cursor position one way or another. When another client sends you their cursor though, you need to know one other piece of information and that's which version of the document did that cursor come from? If the cursor is a position on a document that you haven't seen yet, then you can't draw it yet because you might not actually have that position in your document. Like if a client on version two sent you a position 15, but your document only has 10 characters in it, like how are you supposed to draw that? So you can either hold onto that cursor for later until you see that document or if your cursors are being sent all the time, you can just drop it and hope that it comes back later. If it's from an older version, it also might not apply to your document. And in this case, you have enough information like you could transform it all the way from the version that it came from up to your document and then draw it. But again, like if you have clients that are just sending cursors back and forth all the time, it's probably gonna come back soon enough and you can also just drop it. If it's on the same version, you might think it's all clear and it is, but only if the server has acknowledged all of your operations. And that's because if you're on version 15 and another client is also on version 15, but you have an operation that the server can't have seen yet, like this, you have to transform that cursor when it comes in against any operations that you haven't sent yet to keep it in the right place. What about sending your cursor? Well, kind of the same way, you can send your cursor anytime you want, but only if you haven't done anything you haven't told the server about yet, only if the server knows about every action that you've taken. You can only send it when nothing is pending. Otherwise, like you can see here, your cursor might not make sense to any other clients and they wouldn't be able to draw it in the right place. Once the server confirms that operation, then the other clients can start drawing it again. What about undo? Well, just like cursors, to understand how local undo would work, it makes the most sense to think about how undo would work in general. Remember, since we're thinking in terms of operations, like insert A at position three, when you think about how you wanna undo that, you think remove A at position three. If you wanna redo it, that operation turns into insert A at position three again. These operations are inverses of each other. They cancel each other out. Like if you run an operation and then you run its inverse, it acts as if that original operation never happened, which is exactly what you want undo to do. And undo works like a stack. The last thing you did is the first thing you'll undo. So if our editor wasn't collaborative, here's how applying an operation with undo would work. You would perform an operation like insert HB4 position one. You'd invert that operation so it becomes remove H at position one and then you'd put that inverted operation onto your undo stack. What about when you undo? Well, you'd pop the operation, remove H at position one off the stack. You'd apply it as if you were performing it to begin with. And then if you wanna support undo, you need to invert it again and then push it onto your reuse stack. That's a falseness so that you don't end up pushing undoes onto your undo stack and end up with like weird e-max style undo. So let's see how this breaks when other people are doing things. So you run insert S at position four and that pushes remove S at position four on your undo stack and then you'd send your insert to the server so that other clients can run it on their copy of the document. A little bit later, the server sends you the operation insert H at position one. This isn't happening simultaneously with anything so you don't need to transform it before you apply it and it turns our state, makes our state become charts. But now look at our undo stack. If you undo right now, it would try to remove S at position four but there is no S at position four, right? Is anybody here going to be surprised if I say that we need to do another transformation? So we're missing a step. When you get the operation from the server, you also need to transform your entire undo stack against that operation. So up here, the undo stack is remove S at position four. We receive insert H at position one and we shift that over by one spot so it becomes remove S at position five. And now when you undo, it becomes remove S at position five. We remove the S at position five and everything is good. So when you receive an operation, you have to transform your entire undo and redo stack against that operation. Luckily we already have a function, that big transform one that we wrote earlier that's really good at transforming lists of operations. So we can just use that. So here's local undo. When you perform an operation, you invert it and you push it on the stack. When you receive an operation, you transform the stack against it. And then when you undo, you pop the operation off the stack, you run it and then you send it to the collaboration server so everybody else knows that you did undid something. And this works but it isn't perfect. In fact, this can actually violate some rules that you would normally expect with undo. Like if everybody on the document undid their most recent change and then redid them in a different order, you would expect the document to end up the way that it was before and this doesn't guarantee that. But this turns out to be a pragmatic balance between being complex and having good enough behavior in most cases. And I'm not the only one who thinks so. Almost every collaborative editor I've used including Google Docs ends up having strange undo edge cases like this. Turn, if you can't think of anything, that probably means it's working. And now this is actually enough to make collaboration work with almost any kind of app. You start with a document which can be as simple as an array of things, a cursor, a version, a list of remote cursors and an undo and redo stack. You have operations which act in that state like insert character and remove character. These operations know which version of the document they came from. You have a set of transformation functions which take two operations that happen at the same time and they transform them so that they can be run one after the other in any order. You have a control algorithm which can take two lists of operations and transform each side against each other to come up with documents that end up in the same place. You have functions to transform cursors and functions to send and receive cursors and transform them as they come in. And you have an undo stack and a redo stack which hold inverted operations that get transformed whenever a remote operation comes in. When you perform an operation, putting this all together, you apply it to your document, you transform all your cursors, you send it to the server, you add the inverse to your undo and redo stack and you send your current selection once everything calms down. When you receive an operation, you transform your pending operations against it to complete that transformation square. You apply their transformed operations to your document and you send your transformed operations to their document. You transform all of the cursors that you know about against it and you transform your undo stack against it. When you change your cursor position and you don't have any pending operations like let's say you're hitting the arrow keys or clicking the mouse, you send your current selection. When you get a cursor from somebody else, if it's for an older version, you can ignore it or you can transform it up to your current version. If it's for the current version, you just have to transform it against anything that the server hasn't acknowledged yet. And if it's for a future version, you can also ignore it or you can hold onto it until you see that version of the document. I have an open source demo that puts all this together. You can hit it at this URL and I'll also show this URL again at the end of the talk so I don't feel like you need to rush to type it in. And by the way, it only supports Chrome. Just kidding, it only supports Safari. There are a lot of ways to build collaborative apps and I really like this one. It works for all different kinds of applications. It's extremely flexible and it's not too hard to build. But it's a model that you'll see a lot of different kinds of apps use but it's not perfect. This model needs a server to work or it just doesn't. There are some edge cases especially around undo and redo that would take a lot of complexity if you wanted to fix them. And if you're building specific kinds of apps, there are other kinds of collaboration methods that might be easier or more correct for your case. If you wanna build peer-to-peer collaboration that doesn't depend on the central server, I would highly recommend looking into conflict-free replicated data types. Same thing if you're dealing with just plain text, CRDTs tend to be really well understood and really great at that kind of thing. It's CRDTs are a newer collaboration method that fits some situations really well and are only expanding the scope of things that they can be used well in. If you're using operational transformation, you don't want to write the server or control algorithm yourself and I don't blame you. There's a program called sharedDB that is that kind of transformation server and control algorithm. And if you wanna check out CRDTs, YJS, GunJS, and AutoMerge are all really, really neat projects to go check out. Now I've heard a lot in the past two days about the beauty of remote work and I love that we can do our jobs remotely. Like working remote is one of the greatest changes that I made in my life in the last year. But it does make some things harder. We lose something when we make it harder to work together on a project. And the worst part is when things become harder, they sometimes don't happen at all. I like being able to get a group together to accomplish things that are bigger than any one of us. But that doesn't happen if I'm worried that making a small change is going to wreck your entire day. Collaborative editing is like a magical experience though because all of a sudden it stops feeling like my document or your document, it starts feeling like our document, something we're building together. And I'm confident in making whatever changes I need to because I know that my changes aren't going to conflict with yours. And I want that magic to be there everywhere I go, even if I'm not necessarily using it all the time. So if you're working on a project and you can share things and multiple people can modify things at the same time, like please consider adding this kind of thing to your app because people working on the same thing at the same time should make it better, it shouldn't make it worse. And there's one last thing I wanna mention. Ever since joining AHA, I've worked on some of the most interesting projects that I've ever thought about. And I've only worked on some of the many, many, many projects that we have going on at AHA. So if you like solving cool problems for great customers and you wanna work for a growing, remote, bootstrapped company, come talk to me, we're hiring and I would love to meet you. My email address is up here, use it. I love getting email and responding to email. And if you're going to wanna save one slide, like this is the slide that you wanna take a picture of, it has a link to the demo app, the source code, the slides, a bunch of interesting websites and papers about this topic and lots and lots of other things that I would have loved to talk about but would have turned this into a 37-hour talk. So I did have a ton more that I wanted to talk about but just couldn't fit in. So please, if you see me walking around, come ask me about any of this stuff, ask me questions, tell me about your experiences with collaborative editing, or even just say like, hey Justin, tell me about CRDTs and I will talk your ear off for 37 hours. So thank you so much again for coming and have an awesome day.