Skip to content

Codex: Collaborative Document Editor

Codex: Collaborative Document Editor

Version: 1.0.0 Status: In Development Last Updated: 2026-01-10


Overview

Codex is Orix’s collaborative document editor - a Notion-like workspace built entirely on the Orix platform. It serves as both a productivity tool and a reference implementation demonstrating how to build real-time collaborative applications using Orix’s deterministic infrastructure.

Key Features:

  • Block-Based Editing: Rich documents composed of discrete, typed blocks
  • Real-Time Collaboration: Multiple users editing simultaneously via CRDTs
  • Offline-First: Full functionality without network, sync when reconnected
  • Version History: Time-travel through document history with Chronicle
  • Deterministic: Same operations always produce identical results

The Problem

Traditional Document Editors

1. Operational Transform (OT) Complexity

Most collaborative editors use Operational Transform:

  • Requires central authority server
  • Complex transformation algorithms
  • Doesn’t work offline
  • Latency-sensitive coordination
Client A: Insert "Hello" at position 5
Client B: Delete character at position 3
Server: Must transform operations to maintain consistency
(Complex, error-prone, requires always-on server)

2. No True Offline Support

Google Docs, Notion, and others:

  • Require network connectivity for collaboration
  • Limited offline editing capabilities
  • Conflict resolution happens server-side
  • Users can’t see each other’s changes offline

3. History is an Afterthought

Version history in most editors:

  • Separate system from the main editor
  • Point-in-time snapshots only
  • Can’t explore “what if” scenarios
  • No programmatic access to history

4. Tight Platform Coupling

Editor libraries like ProseMirror, Slate, Quill:

  • Tied to specific JavaScript frameworks
  • Collaboration requires additional libraries
  • No deterministic guarantees
  • Hard to integrate with game/simulation systems

How Codex Solves It

Architecture: CRDT-Native from Day 1

Unlike editors that bolt on collaboration, Codex treats CRDTs as the foundational data model:

Traditional Editor:
Document State → Sync Layer → Server → Other Clients
(State is primary, sync is added)
Codex:
CRDT State ← All Clients Read/Write Directly
(CRDT is the document, inherently collaborative)

Built on Orix Infrastructure

Codex leverages the full Orix stack:

ComponentOrix ProductPurpose
Data TypesLattice.CRDTConflict-free collaborative editing
StorageLattice.DBPersistent document storage
HistoryLattice.ChronicleVersion control and time-travel
SchemaAxionType-safe document/block definitions
SyncNexusReal-time WebSocket synchronization
TestingArbiterDeterministic collaboration tests

What Codex Provides

Architecture Overview

+---------------------------------------------------------------+
| Frontend |
| +----------------------------------------------------------+ |
| | React + Lexical | |
| | - Block components (Paragraph, Heading, List, etc.) | |
| | - Slash command menu | |
| | - Collaborative cursors | |
| +----------------------------------------------------------+ |
| | @orix/crdt-client (TypeScript) | |
| | - CrdtRichText <-> Lexical binding | |
| | - WebSocket sync via Nexus | |
| +----------------------------------------------------------+ |
+---------------------------------------------------------------+
|
| WebSocket
v
+---------------------------------------------------------------+
| Backend |
| +----------------------------------------------------------+ |
| | Codex.Sync (Nexus) | |
| | - WebSocket connections | |
| | - CRDT operation broadcast | |
| | - Presence/awareness | |
| +----------------------------------------------------------+ |
| | Codex.Storage (Lattice) | |
| | - DocumentStore interface | |
| | - FileStorageEngine persistence | |
| | - Chronicle version history | |
| +----------------------------------------------------------+ |
| | Codex.Generated (Axion) | |
| | - Document, Block types | |
| | - BlockType enum | |
| | - TextSpan for rich text marks | |
| +----------------------------------------------------------+ |
+---------------------------------------------------------------+

Component Status

ComponentStatusDescription
Axion SchemaImplementedDocument and Block type definitions
CrdtRichTextImplementedRich text with Peritext-style marks
DocumentStoreImplementedStorage layer with Chronicle integration
Codex.TestsImplemented31 test scenarios
Sync LayerPlannedNexus WebSocket integration
FrontendPlannedReact + Lexical implementation

Document Model

Documents and Blocks

Codex uses a hierarchical block-based structure:

Document
├── Block (Heading1): "Project Overview"
├── Block (Paragraph): "This document describes..."
├── Block (BulletList)
│ ├── Block (Paragraph): "First item"
│ └── Block (Paragraph): "Second item"
├── Block (CodeBlock): "function hello() { ... }"
└── Block (Toggle): "Click to expand"
└── Block (Paragraph): "Hidden content"

Axion Schema

namespace Codex.Schema;
enum BlockType {
Paragraph = 0;
Heading1 = 1;
Heading2 = 2;
Heading3 = 3;
BulletList = 4;
NumberedList = 5;
TodoItem = 6;
CodeBlock = 7;
Quote = 8;
Divider = 9;
Image = 10;
Table = 11;
Callout = 12;
Toggle = 13;
PageEmbed = 14;
LinkEmbed = 15;
}
record Document {
id: guid;
title: string;
icon: string?;
cover: string?;
parentId: guid?;
order: string; // Fractional indexing
isTrashed: bool;
createdAt: int64;
updatedAt: int64;
}
record Block {
id: guid;
documentId: guid;
blockType: BlockType;
parentId: guid?;
order: string; // Fractional indexing
plainText: string; // Searchable text content
marks: list<TextSpan>; // Rich text formatting
createdAt: int64;
updatedAt: int64;
}
record TextSpan {
start: int32;
end: int32;
markType: MarkType;
value: string?;
}
enum MarkType {
Bold = 0;
Italic = 1;
Underline = 2;
Strikethrough = 3;
Code = 4;
Link = 5;
Color = 6;
Highlight = 7;
}

Storage Layer

IDocumentStore Interface

public interface IDocumentStore : IDisposable
{
// Document Operations
Document? GetDocument(Guid id);
IReadOnlyList<Document> GetRootDocuments();
IReadOnlyList<Document> GetChildDocuments(Guid parentId);
void SaveDocument(Document document);
bool DeleteDocument(Guid id);
IReadOnlyList<Document> SearchDocuments(string query);
// Block Operations
Block? GetBlock(Guid id);
IReadOnlyList<Block> GetDocumentBlocks(Guid documentId);
IReadOnlyList<Block> GetChildBlocks(Guid parentId);
IReadOnlyList<Block> GetRootBlocks(Guid documentId);
void SaveBlock(Block block);
void SaveBlocks(IEnumerable<Block> blocks);
bool DeleteBlock(Guid id);
IReadOnlyList<Block> SearchBlocks(string query);
// Version History (Chronicle)
ulong CreateSnapshot(string? label = null);
IReadOnlyList<SnapshotInfo> GetSnapshots();
Document? GetDocumentAtSnapshot(Guid documentId, ulong snapshotId);
IReadOnlyList<Block> GetBlocksAtSnapshot(Guid documentId, ulong snapshotId);
// Maintenance
void Flush();
StorageInfo GetStorageInfo();
}

Key Organization

Documents and blocks are stored with efficient key prefixes for queries:

PrefixPurposeExample Key
doc:Document by IDdoc:{guid}
blk:Block by IDblk:{guid}
dch:Document childrendch:{parentId}:{order}:{childId}
bch:Block childrenbch:{parentId}:{order}:{childId}
bdc:Blocks in documentbdc:{documentId}:{order}:{blockId}

Usage Example

using Codex;
using Codex.Generated;
// Create store with versioning enabled
using var store = new DocumentStore("workspace.db", enableVersioning: true);
// Create a document
var doc = new Document
{
Id = Guid.NewGuid(),
Title = "Meeting Notes",
Order = "a0",
CreatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
UpdatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
};
store.SaveDocument(doc);
// Add blocks
var heading = new Block
{
Id = Guid.NewGuid(),
DocumentId = doc.Id,
BlockType = BlockType.Heading1,
PlainText = "Weekly Standup",
Order = "a0",
Marks = new List<TextSpan>(),
CreatedAt = doc.CreatedAt,
UpdatedAt = doc.UpdatedAt
};
store.SaveBlock(heading);
var paragraph = new Block
{
Id = Guid.NewGuid(),
DocumentId = doc.Id,
BlockType = BlockType.Paragraph,
PlainText = "Discussed project timeline and blockers.",
Order = "a1",
Marks = new List<TextSpan>
{
new TextSpan { Start = 10, End = 17, MarkType = MarkType.Bold }
},
CreatedAt = doc.CreatedAt,
UpdatedAt = doc.UpdatedAt
};
store.SaveBlock(paragraph);
// Create a snapshot
var snapshotId = store.CreateSnapshot("Initial version");
// Query documents
var results = store.SearchDocuments("meeting");
foreach (var result in results)
{
Console.WriteLine($"Found: {result.Title}");
}
// Time-travel: get document at snapshot
var historicDoc = store.GetDocumentAtSnapshot(doc.Id, snapshotId);

CRDT Rich Text

CrdtRichText for Collaborative Editing

Codex uses CrdtRichText - a Peritext-style implementation built on Rga<char>:

using Lattice.CRDT;
var clock = new HybridLogicalClock("user-1");
var text = new CrdtRichText("user-1", clock);
// Insert text
text.Insert(0, "Hello World");
// Apply formatting (marks)
text.AddMark(0, 5, new TextMark(MarkType.Bold)); // "Hello" is bold
text.AddMark(6, 11, new TextMark(MarkType.Italic)); // "World" is italic
// Remove formatting
text.RemoveMark(0, 5, MarkType.Bold);
// Get content
string plainText = text.ToString(); // "Hello World"
var marks = text.GetMarksAt(0); // Check formatting at position
// Merge from another user
var otherText = new CrdtRichText("user-2", new HybridLogicalClock("user-2"));
otherText.Insert(0, "Hi ");
text.Merge(otherText); // Automatically resolves conflicts

Mark Behavior (Peritext Model)

Marks follow Peritext semantics for intuitive collaborative behavior:

Mark TypeExpand LeftExpand RightBehavior
BoldNoYesTyping at end extends bold
ItalicNoYesTyping at end extends italic
LinkNoNoTyping at edges doesn’t extend link
CodeNoNoInline code has fixed boundaries
Before: "Hello [World]" (World is bold)
User types "!" at position 11
After: "Hello [World!]" (bold extends to include "!")

Version History with Chronicle

Creating Snapshots

// Manual snapshots for significant changes
var snapshotId = store.CreateSnapshot("Before major refactor");
// Snapshots are compressed and include:
// - All documents
// - All blocks
// - Metadata (tick, timestamp, label)

Time-Travel Queries

// List all snapshots
foreach (var snapshot in store.GetSnapshots())
{
Console.WriteLine($"Snapshot {snapshot.Id}: {snapshot.Label}");
Console.WriteLine($" Created: {snapshot.CreatedAt}");
}
// Get document at specific version
var oldVersion = store.GetDocumentAtSnapshot(docId, snapshotId);
var oldBlocks = store.GetBlocksAtSnapshot(docId, snapshotId);
// Compare versions
var currentDoc = store.GetDocument(docId);
Console.WriteLine($"Old title: {oldVersion?.Title}");
Console.WriteLine($"New title: {currentDoc?.Title}");

Use Cases

  • Undo/Redo: Restore to previous snapshot
  • Audit Trail: See who changed what, when
  • Branching: Create “what-if” document variants
  • Recovery: Restore accidentally deleted content

Advantages

1. True Offline-First

No network dependency for editing:

  • Full functionality offline
  • Changes stored locally
  • Automatic sync when reconnected
  • No “you’re offline” warnings

2. Deterministic Collaboration

CRDTs guarantee convergence:

  • No central authority needed
  • Same operations = same result
  • No conflict resolution UI
  • Peer-to-peer capable

3. Built-In Time Travel

Chronicle integration means:

  • Every change is tracked
  • Navigate to any point in history
  • Branch to explore alternatives
  • Programmatic history access

4. Schema-First Types

Axion schemas provide:

  • Compile-time type safety
  • Automatic serialization
  • Cross-platform compatibility
  • No runtime type mismatches

5. Dogfooding Orix

Codex demonstrates:

  • Lattice.CRDT for collaboration
  • Lattice.DB for storage
  • Chronicle for versioning
  • Nexus for sync (planned)

Disadvantages

1. Storage Overhead

CRDTs require additional metadata:

  • Tombstones for deleted content
  • Vector clocks for causality
  • Multiple versions of marks
  • ~20-50% overhead vs plain text

Mitigation: Garbage collection removes old tombstones

2. Complexity vs Simple Editors

More complex than basic text editing:

  • CRDT semantics to understand
  • Mark behavior (Peritext model)
  • Snapshot management
  • Sync protocol

Mitigation: Abstractions hide complexity from end users

3. Limited Rich Text Features (Currently)

Current implementation supports:

  • Basic marks (bold, italic, underline, etc.)
  • Block types (paragraphs, headings, lists)

Not yet implemented:

  • Tables with cell-level editing
  • Embedded media
  • Comments/suggestions
  • Real-time cursors

4. Frontend In Progress

Backend is implemented, frontend planned:

  • React + Lexical integration
  • Slash command palette
  • Drag-and-drop blocks
  • Collaborative cursors

Comparison with Other Editors

vs Notion

FeatureNotionCodex
CollaborationOT (server-required)CRDT (peer-to-peer capable)
OfflineLimitedFull functionality
History30 days (paid)Unlimited (Chronicle)
Self-hostedNoYes
Open sourceNoYes
DeterministicNoYes

vs Google Docs

FeatureGoogle DocsCodex
Real-timeYes (OT)Yes (CRDT)
OfflineLimitedFull
Block-basedNoYes
Self-hostedNoYes
API accessLimitedFull programmatic access

vs Yjs-based Editors

FeatureYjs + LexicalCodex
CRDTYjs (JavaScript)Lattice.CRDT (C#)
Time-travelAdd-onBuilt-in (Chronicle)
SchemaRuntimeCompile-time (Axion)
DeterministicNo guaranteesGuaranteed
PlatformJavaScript onlyCross-platform

Roadmap

Phase 1: Foundation (Current)

  • Axion schema for documents/blocks
  • CrdtRichText with Peritext marks
  • DocumentStore with Lattice.DB
  • Chronicle integration for history
  • Unit tests (31 scenarios)

Phase 2: Sync Layer

  • Nexus WebSocket integration
  • CRDT operation broadcast
  • Presence/awareness protocol
  • Conflict-free sync

Phase 3: Frontend

  • React + Lexical integration
  • Block components
  • Slash command menu
  • @orix/crdt-client TypeScript package

Phase 4: Polish

  • Collaborative cursors
  • Comments and suggestions
  • Export (Markdown, PDF)
  • Import (Notion, Markdown)

Code Example: Complete Integration

using Codex;
using Codex.Generated;
using Lattice.CRDT;
// Initialize storage
using var store = new DocumentStore("workspace.db", enableVersioning: true);
// Create workspace document
var workspace = new Document
{
Id = Guid.NewGuid(),
Title = "My Workspace",
Order = "a0",
CreatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
UpdatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
};
store.SaveDocument(workspace);
// Create collaborative rich text
var clock = new HybridLogicalClock("user-1");
var richText = new CrdtRichText("user-1", clock);
// User 1 types
richText.Insert(0, "Hello World");
richText.AddMark(0, 5, new TextMark(MarkType.Bold));
// Simulate User 2's concurrent edit
var user2Clock = new HybridLogicalClock("user-2");
var user2Text = new CrdtRichText("user-2", user2Clock);
user2Text.Insert(0, "Hi ");
// Merge changes (CRDT magic - no conflicts!)
richText.Merge(user2Text);
// Create block from rich text
var block = new Block
{
Id = Guid.NewGuid(),
DocumentId = workspace.Id,
BlockType = BlockType.Paragraph,
PlainText = richText.ToString(),
Marks = richText.GetAllMarks().Select(m => new TextSpan
{
Start = m.Start,
End = m.End,
MarkType = m.Mark.Type
}).ToList(),
Order = "a0",
CreatedAt = workspace.CreatedAt,
UpdatedAt = workspace.UpdatedAt
};
store.SaveBlock(block);
// Create version snapshot
store.CreateSnapshot("Initial content");
// Search across all documents
var searchResults = store.SearchBlocks("World");
Console.WriteLine($"Found {searchResults.Count} blocks containing 'World'");
// Get storage stats
var info = store.GetStorageInfo();
Console.WriteLine($"Documents: {info.DocumentCount}");
Console.WriteLine($"Blocks: {info.BlockCount}");
Console.WriteLine($"Snapshots: {info.SnapshotCount}");
Console.WriteLine($"Total size: {info.TotalBytes} bytes");


Document Version: 1.0.0 Status: Complete Next Review: 2026-02-01