Codex: Architecture Decision Record
Codex Editor Architecture
Status: DRAFT Issue: ORI-77 Date: 2026-01-09
Summary
This document evaluates block-based editor architectures for Codex, a Notion-like collaborative workspace built on the Orix platform.
Research Findings
1. ProseMirror
Architecture: Schema-based document model with nodes and marks
Collaboration: Operational Transform (OT) via prosemirror-collab plugin
- Central authority validates and broadcasts “steps”
- Version tracking for conflict detection
- Requires server-side coordination
Pros:
- Mature, well-documented
- Highly customizable schema
- Large ecosystem
Cons:
- Collaboration is an “addon”, not native
- OT requires central authority (not offline-first)
- Complex for block-based UIs
2. Lexical (Meta)
Architecture: Node-based (ElementNode, TextNode, DecoratorNode)
Collaboration: Via Yjs binding (@lexical/yjs)
Pros:
- Modern, actively maintained
- React-first with good DX
- Extensible node system
- JSON serialization built-in
Cons:
- Less mature than ProseMirror
- Collaboration requires external CRDT library
3. Yjs (CRDT Library)
Architecture: True CRDT with shared types (Y.Doc, Y.Text, Y.Map, Y.Array)
Key Features:
- Conflict-free automatic merging
- Offline-first by design
- No central authority needed
- Quill Delta format for rich text
Shared Types:
Y.Text // Rich text with formattingY.Map // Key-value storeY.Array // Ordered listY.XmlFragment // Tree structurePros:
- True offline-first
- Battle-tested (used by many editors)
- Language-agnostic protocol
Cons:
- JavaScript only (needs port for C#)
- Storage format not directly compatible with Axion
4. BlockSuite (AFFiNE)
Architecture: Document-centric, CRDT-native
Key Innovation: CRDT IS the data layer, not an addon
Traditional: Editor State → Sync Layer → ServerBlockSuite: Y.Doc (CRDT) → All Editors Read From ItBlock Model:
- Discrete contenteditable blocks
- Each block connects to Y.Text node
- Tree structure via Y.XmlFragment
Pros:
- Purpose-built for Notion-like editing
- CRDT-native (Yjs built-in)
- Block-based from the ground up
- Web components (framework-agnostic)
Cons:
- Tightly coupled to Yjs
- Less flexible than lower-level options
Key Architecture Questions
Q1: How do blocks map to CRDTs?
Answer: Each block is a node in a Y.XmlFragment tree. Block content (rich text) maps to Y.Text. Block metadata maps to Y.Map.
Y.Doc└── Y.XmlFragment (page) ├── Y.XmlElement (paragraph block) │ ├── Y.Text (content) │ └── Y.Map (metadata) ├── Y.XmlElement (heading block) │ └── ... └── Y.XmlElement (list block) └── ...Q2: Schema for nested blocks?
Answer: Tree structure using parent references or nested Y.XmlFragment:
interface Block { id: string; type: BlockType; parentId: string | null; children: string[]; // Block IDs content: Y.Text; props: Y.Map;}Q3: Cursor positions in collaborative editing?
Answer: Awareness protocol (Yjs) or custom presence system:
- Each client broadcasts cursor position
- Positions stored as relative CRDT positions (survive edits)
- Render remote cursors with user colors
Q4: Performance with large documents?
Answer:
- Lazy loading: Only load visible blocks
- Virtualization: Render only viewport
- Chunked sync: Don’t send entire document
- Yjs handles 10K+ operations efficiently
Recommendation
Strategic Decision: Build vs Buy
| Approach | Pros | Cons |
|---|---|---|
| A: Use BlockSuite/Yjs | Fast MVP, proven tech | Not dogfooding Orix |
| B: Custom CRDT on Orix | Full dogfooding, control | More work, risk |
| C: Hybrid | Best of both | Integration complexity |
Recommended: Option B (Dogfood from Day 1)
UPDATE: With the discovery of existing Lattice.CRDT infrastructure, we can dogfood Orix from day 1.
Phase 1 (Foundation): Use Lattice.CRDT + Lexical for frontend
- Leverage existing
Rga<T>for text sequences - Use
OrMap<K,V>andNestedOrMap<K,V>for blocks - Build
CrdtRichTextadapter for Lexical integration - Sync via Nexus WebSocket
Phase 2 (Persistence): Full Lattice integration
- Store in LatticeDB with Chronicle history
- Implement
CrdtTreefor block hierarchy - Add tombstone garbage collection
Phase 3 (Differentiation): Orix-native features
- Deterministic replay of editing sessions
- Time-travel debugging for documents
- Cross-document CRDT references
Proposed Architecture
Data Layer (Orix)
┌─────────────────────────────────────────┐│ Codex.Storage │├─────────────────────────────────────────┤│ Orix.Lattice.Crdt ││ ├── CrdtText (rich text) ││ ├── CrdtMap (block props) ││ └── CrdtTree (block hierarchy) │├─────────────────────────────────────────┤│ Orix.Lattice.Chronicle ││ └── Version history, snapshots │├─────────────────────────────────────────┤│ Orix.Axion ││ └── codex.axion schema definitions │└─────────────────────────────────────────┘Sync Layer (Orix)
┌─────────────────────────────────────────┐│ Codex.Sync │├─────────────────────────────────────────┤│ Orix.Nexus ││ ├── WebSocket connections ││ ├── CRDT operation broadcast ││ └── Presence/awareness │└─────────────────────────────────────────┘Frontend
┌─────────────────────────────────────────┐│ codex-web │├─────────────────────────────────────────┤│ React + Lexical ││ ├── Block components ││ ├── Slash command menu ││ └── Collaborative cursors │├─────────────────────────────────────────┤│ @orix/crdt-client (TypeScript) ││ ├── CrdtRichText ↔ Lexical binding ││ └── WebSocket sync via Nexus │└─────────────────────────────────────────┘Axion Schema (Draft)
namespace Codex.Schema;
// Block typesenum 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;}
// Documentrecord Document { id: Entity; title: string; icon: string?; cover: string?; parentId: Entity?; rootBlockId: Entity; createdAt: Tick; updatedAt: Tick;}
// Blockrecord Block { id: Entity; documentId: Entity; type: BlockType; parentId: Entity?; order: DFixed64; // Fractional indexing for reordering props: map<string, string>; // Content stored separately as CRDT}Blocking Dependencies
Before Codex implementation can proceed:
| Blocker | Issue | Status |
|---|---|---|
| ORI-52 | ✅ COMPLETE - Lattice.CRDT exists | |
| Lattice alpha deployment | ORI-49 | Backlog |
| Nexus sync implementation | (new) | Not created |
Existing CRDT Infrastructure (Lattice.CRDT)
Discovery: The Orix codebase already contains a comprehensive CRDT library:
| Component | Class | Codex Use |
|---|---|---|
| HybridLogicalClock | HlcTimestamp | Causality tracking |
| Sequence CRDT | Rga<T> | Text content |
| Add-wins Set | OrSet<T> | Block collections |
| OR-Map | OrMap<K,V> | Block properties |
| Nested OR-Map | NestedOrMap<K,V> | Block hierarchy |
| LWW Register | LwwRegister<T> | Simple fields |
| MV Register | MvRegister<T> | Concurrent edits |
| Merge Engine | MergeEngine | Entity merging |
Remaining for Codex:
CrdtRichText- Rich text with marks (extendsRga<T>)CrdtTree- Block hierarchy (usingNestedOrMap+ fractional indexing)
Next Steps
- ORI-78: Finalize Axion schema based on this architecture
ORI-52: Design CRDT strategy for Lattice✅ DONE - Lattice.CRDT exists!- ORI-79: Implement
CrdtRichTextextendingRga<T>with formatting marks - ORI-82: Start frontend with Lexical +
@orix/crdt-client(can parallel)