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 5Client B: Delete character at position 3Server: 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:
| Component | Orix Product | Purpose |
|---|---|---|
| Data Types | Lattice.CRDT | Conflict-free collaborative editing |
| Storage | Lattice.DB | Persistent document storage |
| History | Lattice.Chronicle | Version control and time-travel |
| Schema | Axion | Type-safe document/block definitions |
| Sync | Nexus | Real-time WebSocket synchronization |
| Testing | Arbiter | Deterministic 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
| Component | Status | Description |
|---|---|---|
| Axion Schema | Implemented | Document and Block type definitions |
| CrdtRichText | Implemented | Rich text with Peritext-style marks |
| DocumentStore | Implemented | Storage layer with Chronicle integration |
| Codex.Tests | Implemented | 31 test scenarios |
| Sync Layer | Planned | Nexus WebSocket integration |
| Frontend | Planned | React + 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:
| Prefix | Purpose | Example Key |
|---|---|---|
doc: | Document by ID | doc:{guid} |
blk: | Block by ID | blk:{guid} |
dch: | Document children | dch:{parentId}:{order}:{childId} |
bch: | Block children | bch:{parentId}:{order}:{childId} |
bdc: | Blocks in document | bdc:{documentId}:{order}:{blockId} |
Usage Example
using Codex;using Codex.Generated;
// Create store with versioning enabledusing var store = new DocumentStore("workspace.db", enableVersioning: true);
// Create a documentvar doc = new Document{ Id = Guid.NewGuid(), Title = "Meeting Notes", Order = "a0", CreatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), UpdatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()};store.SaveDocument(doc);
// Add blocksvar 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 snapshotvar snapshotId = store.CreateSnapshot("Initial version");
// Query documentsvar results = store.SearchDocuments("meeting");foreach (var result in results){ Console.WriteLine($"Found: {result.Title}");}
// Time-travel: get document at snapshotvar 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 texttext.Insert(0, "Hello World");
// Apply formatting (marks)text.AddMark(0, 5, new TextMark(MarkType.Bold)); // "Hello" is boldtext.AddMark(6, 11, new TextMark(MarkType.Italic)); // "World" is italic
// Remove formattingtext.RemoveMark(0, 5, MarkType.Bold);
// Get contentstring plainText = text.ToString(); // "Hello World"var marks = text.GetMarksAt(0); // Check formatting at position
// Merge from another uservar otherText = new CrdtRichText("user-2", new HybridLogicalClock("user-2"));otherText.Insert(0, "Hi ");text.Merge(otherText); // Automatically resolves conflictsMark Behavior (Peritext Model)
Marks follow Peritext semantics for intuitive collaborative behavior:
| Mark Type | Expand Left | Expand Right | Behavior |
|---|---|---|---|
| Bold | No | Yes | Typing at end extends bold |
| Italic | No | Yes | Typing at end extends italic |
| Link | No | No | Typing at edges doesn’t extend link |
| Code | No | No | Inline 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 changesvar snapshotId = store.CreateSnapshot("Before major refactor");
// Snapshots are compressed and include:// - All documents// - All blocks// - Metadata (tick, timestamp, label)Time-Travel Queries
// List all snapshotsforeach (var snapshot in store.GetSnapshots()){ Console.WriteLine($"Snapshot {snapshot.Id}: {snapshot.Label}"); Console.WriteLine($" Created: {snapshot.CreatedAt}");}
// Get document at specific versionvar oldVersion = store.GetDocumentAtSnapshot(docId, snapshotId);var oldBlocks = store.GetBlocksAtSnapshot(docId, snapshotId);
// Compare versionsvar 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
| Feature | Notion | Codex |
|---|---|---|
| Collaboration | OT (server-required) | CRDT (peer-to-peer capable) |
| Offline | Limited | Full functionality |
| History | 30 days (paid) | Unlimited (Chronicle) |
| Self-hosted | No | Yes |
| Open source | No | Yes |
| Deterministic | No | Yes |
vs Google Docs
| Feature | Google Docs | Codex |
|---|---|---|
| Real-time | Yes (OT) | Yes (CRDT) |
| Offline | Limited | Full |
| Block-based | No | Yes |
| Self-hosted | No | Yes |
| API access | Limited | Full programmatic access |
vs Yjs-based Editors
| Feature | Yjs + Lexical | Codex |
|---|---|---|
| CRDT | Yjs (JavaScript) | Lattice.CRDT (C#) |
| Time-travel | Add-on | Built-in (Chronicle) |
| Schema | Runtime | Compile-time (Axion) |
| Deterministic | No guarantees | Guaranteed |
| Platform | JavaScript only | Cross-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 storageusing var store = new DocumentStore("workspace.db", enableVersioning: true);
// Create workspace documentvar 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 textvar clock = new HybridLogicalClock("user-1");var richText = new CrdtRichText("user-1", clock);
// User 1 typesrichText.Insert(0, "Hello World");richText.AddMark(0, 5, new TextMark(MarkType.Bold));
// Simulate User 2's concurrent editvar 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 textvar 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 snapshotstore.CreateSnapshot("Initial content");
// Search across all documentsvar searchResults = store.SearchBlocks("World");Console.WriteLine($"Found {searchResults.Count} blocks containing 'World'");
// Get storage statsvar 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");Related Documents
- Codex Architecture ADR - Technical decision record
- LatticeDB - Storage and CRDT infrastructure
- Chronicle - Time-travel engine
- Axion - Schema language
- Nexus - Networking layer
Document Version: 1.0.0 Status: Complete Next Review: 2026-02-01