I've just found a surprising bug of sorts in my OT code. It turns out compose has peculiar side effects in terms of information loss / gain.
Imagine you have a compose function '+', infix transform function 'T', server op 's' and client ops 'c1' and 'c2', its possible that: s T c1 T c2 != s T (c1 + c2) Wave's concurrency control algorithms never do this, so its not a problem in practice. Is this normal? It never occurred to me that this would be the case... If anyone is curious, I've reproduced this behaviour using WIAB's docops (below). -J ... Basically, it happens if s = insert: 's', skip: 1 c1 = delete: 'x' c2 = insert: 'c' // Make println() usable. DocOpScrub.setShouldScrubByDefault(false); DocOp s = new DocOpBuilder().characters("s").retain(1).build(); DocOp c1 = new DocOpBuilder().deleteCharacters("x").build(); DocOp c2 = new DocOpBuilder().characters("c").build(); DocOp cc = Composer.compose(c1, c2); // s_ = s T c1 DocOp s_ = Transformer.transform(c1, s).serverOp(); // s__ = s T c1 T c2 DocOp s__ = Transformer.transform(c2, s_).serverOp(); // cc = c1 + c2 System.out.println("cc: " + cc); // scc_ = s T (c1 + c2) DocOp scc_ = Transformer.transform(cc, s).serverOp(); // s__ and scc_ are different! System.out.println("s__: " + s__); System.out.println("scc_: " + scc_); System.out.println(); // ... And not just different syntactically. They're different semantically. DocOp doc1 = Composer.compose(ImmutableList.of(new DocOpBuilder().characters("x").build(), c1, c2, s__)); System.out.println("doc1: " + doc1); DocOp doc2 = Composer.compose(ImmutableList.of(new DocOpBuilder().characters("x").build(), cc, scc_)); System.out.println("doc2: " + doc2);