In 1999, Udanax-Gold was open-sourced — 200,000 lines of mechanically-translated Smalltalk-to-C++ with elegant data structures but no working transclusion queries. Here is everything we built on top.
Gold was single-user only. Xudanu supports real-time collaborative editing via otree-merge, a three-way merge that operates directly on O-tree elements — not flattened text. Collaborative edits preserve transclusions, overlays, data bindings, and attribution.
When two users edit the same document concurrently, the server performs a three-way merge against a common base:
Figure 1: Two concurrent edits merged via three-way fingerprint alignment.
pub enum MergeSegment<T> {
Unchanged(T),
OnlyA(T),
OnlyB(T),
Conflict { a: T, b: T },
Insert { pos: usize, elements: Vec<T> },
}
src/edition/three_way.rs. The CRDT manager that wraps it for collaborative editing is in src/server/otree_crdt.rs.
Every edit in Xudanu is cryptographically signed with an Ed25519 key. The challenge: how does a web browser hold a signing key? The answer is a carefully designed auth chain where the key is encrypted at rest, decrypted on login, and cached in the server-side session.
authenticate_with_pending uses Argon2id + ChaCha20-Poly1305 AEADOAuthSession.signing_key_bytes stores the Ed25519 key bytes server-sidexudanu_session cookie with CSRF token provided separatelyauthenticate_session_from_oauth from cached bytesmaterialize_with_provenance finds the key, signs every editsession_login_public for anonymous browsing if no cookie presentFigure 2: Auth chain from login through WebSocket auto-authentication.
oauth2 crate v5. Club signing keys are encrypted at rest with Argon2id key derivation + ChaCha20-Poly1305 AEAD. Rate-limited login: 10 attempts per 5 minutes per club.
Gold had no cryptographic attribution — authorship was asserted, not proven. Xudanu signs every edit with Ed25519. An append-only SHA-256 hash-chained transparency log records every provenance signature, making forgery detectable after the fact.
| Type | Color | Details |
|---|---|---|
| Human | Green | Ed25519 keypair, display name from club profile. Every keystroke attributed to a verified identity. |
| LLM | Purple | Model name tagged in provenance. AI-generated text visually distinguished. Supports Ollama (local) and OpenRouter (cloud). |
| Historical | Amber | Shakespeare, Austen, etc. Server-signed attestations. Generic external_ids field for authority database lookups (not yet populated). |
ElementProvenance {
signature: Signature, // Ed25519
author_key: PublicKey,
author_name: String,
author_type: AuthorType,
// Human | Llm | Historical
timestamp: u64,
server_id: String,
}
// Signature covers:
// BLAKE3(
// fingerprint
// || author_key
// || timestamp
// || server_id
// )
// with domain separation
Every provenance signature is recorded in an append-only SHA-256 hash-chained log. Each entry includes the hash of the previous entry — tampering with any single entry breaks the entire chain from that point forward.
Figure 3: SHA-256 hash-chained transparency log for provenance signatures.
Gold used in-memory Smalltalk object storage with no persistence. Xudanu stores document content as individually addressable .xchunk files in hex-sharded directories, each verified by BLAKE3 hash. Checkpoints write only changed chunks, not the entire state.
xudanu-data/ ├── chunks/ │ ├── ab/ │ │ └── ab3f...c821.xchunk │ ├── 7e/ │ │ └── 7e2a...f409.xchunk │ └── ... ├── works/ │ ├── work-001.json │ └── ... ├── manifest.json └── audit.log
| Property | How |
|---|---|
| Deduplication | Identical content stored once (content-addressed via BLAKE3) |
| Hex-sharded directories | First 2 hex chars of BLAKE3 hash determine the subdirectory |
| Incremental checkpoints | Write only changed chunks, not entire state |
| LRU cache | Capacity 1024 for hot chunks in memory |
| Atomic writes | tmp file + rename prevents corruption on crash |
| BLAKE3 verification | Every chunk verified on read by hash comparison |
| Garbage collection | Orphaned chunks cleaned up alongside annotation cleanup |
| Off-site backup | backup-chunks.sh syncs immutable chunks + manifests to a remote server via rsync/SSH. Content-addressed design makes incremental backup safe — chunks are write-once, BLAKE3 hash = filename. |
scp or a secrets manager). Completing federation would provide automatic multi-node replication as an alternative.
src/persist/. The manifest with annotations_hash field is in src/persist/manifest.rs. The backup script is at examples/backup-chunks.sh.
A full CRUD annotation system operating on character ranges. Yellow highlights in the editor, annotation panel with author attribution, keyboard shortcut Ctrl+Alt+A / Cmd+Alt+A. Annotations are a metadata layer — they don't modify document content — so they work on source works too.
pub struct Annotation {
id: String,
doc_id: String,
start: usize, // char offset
end: usize, // char offset
text: String, // annotation body
color: Option<String>,
created_by: String, // club ID
created_by_name: String, // display name
created_at: u64,
}
annotations_hash in manifest, chunk-storedcreated_by_name resolved from club display namepointer-events: none when no annotations to drawsrc/server/otree_crdt.rs. The frontend annotation panel is in web/app/src/components/AnnotationPanel.tsx.
Gold had a terminal/Motif UI. Xudanu has a React SPA with TypeScript, real-time collaborative editing via WebSocket, canvas-based attribution overlays, and side-by-side comparison with bridge curves connecting transcluded regions.
| Component | What It Does |
|---|---|
| Editor | contentEditable with canvas overlay for provenance colors, transclusion markers, and annotation highlights. pointer-events: none when no overlays — cursor placement always works. |
| Compare | Side-by-side document comparison with bridge curves connecting transcluded regions. Shared (amber), left-only (blue), right-only (orange). Edit while comparing — highlights update live. |
| Source Viewer | Read-only source work display with smooth scrolling (5 lines/tick wheel normalization). Green "pub src" badge. Annotations overlay supported. |
| Annotations | Yellow highlight selection, annotation panel with CRUD, author names. Keyboard shortcut Ctrl+Alt+A. |
| Privacy | Documents default to private ("PRIV" badge). Share menu toggles public/private. Signed-out users see only public works. |
| Deep Links | SPA with ?work=<id> query param and #L<line> / #C<char> hash deep links. No react-router. |
110+ operation codes covering sessions, clubs, works, editions, links, transclusion, blobs, overlays, labels, retrieval, attribution, search, shared content, range queries, versioning, recorders, crypto, endorsements, federation, membership, governance, and CRDT sync. Dual codec: binary (LEB128 + postcard) and JSON.
Pattern-based detection for Project Gutenberg, Internet Archive, and plain text. Registry for non-digital authors with external IDs. Content boundary detection to skip header/footer boilerplate. Source works are permanently frozen — no edits allowed. Content matching on paste automatically attributes provenance to historical authors.
Historical and the external ID recorded.
external_ids: HashMap<String, String> field for authority database lookups, and both VIAF and LoC expose free public APIs that return stable IDs from an author name (e.g. VIAF AutoSuggest returns viafid and lc IDs). However, the ImportWizard does not yet call these APIs — the field is always empty. This is a natural enhancement for the author creation step.
Multi-backend support for AI-assisted editing. All LLM output is cryptographically tagged in the provenance system with AuthorType::Llm and the model name.
| Feature | Details |
|---|---|
| Local models | Ollama integration for running models on the developer's machine |
| Cloud models | OpenRouter and GitHub Models for cloud-based LLMs |
| Diff narration | "Summarize changes" between two document versions |
| Auto-title generation | Generate document titles from content |
| Writing feedback | On-demand feedback and suggestions |
| Provenance tagging | All LLM output tagged with model name and AuthorType::Llm in the provenance system |
Personal "clubs" with Ed25519 keypairs encrypted at rest. Gold had no concept of user identity or access control.
| Feature | Implementation |
|---|---|
| Key encryption at rest | Argon2id key derivation + ChaCha20-Poly1305 AEAD |
| Password hashing | Argon2id with PHC-string format |
| Rate limiting | 10 login attempts per 5 minutes per club |
| OAuth2 | GitHub and Google login via oauth2 crate v5 |
| CSRF protection | CSRF token required on WebSocket connections |
| Audit log | SHA-256 chained tamper-evident log of security events |
| Session cookies | HttpOnly, validated on every request |
| Threat classification | Threat-level classification for security events |
| Password policy | Minimum 10 chars, mixed case + digit (client + server enforced) |
Encrypted peer channels (X25519 + ChaCha20-Poly1305). OrSet and LwwRegister CRDTs for state reconciliation. PBFT governance for membership and policy decisions. Cross-server transclusion queries. Transport layer exists; content sync is incomplete. Once finished, federation would provide automatic multi-node replication — an alternative to the manual off-site backup script for data redundancy.
The Udanax team designed elegant data structures but left core queries unimplemented. The BackfollowEngine had no callers. H-tree canopy flags were allocated but never checked. Xudanu connects the pieces and makes them fast.
| Operation | Naive (C++ parity) | Xudanu | How |
|---|---|---|---|
| Find works containing text | O(W × L) | O(U × A) | Fingerprint HashMap intersection, no full scan |
| Find shared regions (diff) | O(n × m) | O(La + Lb) | Fingerprint seed discovery + greedy claiming |
| Edition element listing | O(n log n) every call | O(1) after first | OnceLock cache shared via Arc |
| Transclusion index lookup | O(n) flat scan | O(log n) | H-tree canopy-filtered traversal |
| Position transform (shift) | O(n) rewrite | O(1) | Lazy Dsp node wraps tree without traversal |
| Region highlighting | O(r × t) | O(r + t) | Sorted regions, single pass |
W = works, L = text length, U = unique fingerprints, A = avg works/fingerprint, n/m = doc lengths, r = regions, t = text
How closely does Xudanu implement Ted Nelson's original 17 Rules of Xanadu? Here is the honest accounting.
| # | Rule | Status |
|---|---|---|
| 1 | Every document consists of arbitrarily many portions | Done — O-tree elements |
| 2 | Every portion can be transcluded | Done — inline RangeElement::Transclusion, 32-level recursive resolution |
| 3 | Permission is required to transclude | Done — club-based ACLs |
| 4 | Permission is required to quote | Done — source import + attribution |
| 5 | Tumbler addressing | Partial — Id/IdSpace, no full tumbler paths |
| 6 | Bidirectional links | Done — HyperLink/HyperRef |
| 7 | Visible on both sides | Done — backlinks + Related footer (always-visible) |
| 8 | Identical content stored once | Done — GrandMap + BLAKE3 |
| 9 | Royalty at any granularity | Partial — data structures exist, no payments |
| 10 | Mix of royalty-bearing and free content | Partial — endorsement types exist |
| 11 | Data available from any server | Partial — federation transport, incomplete sync |
| 12 | Connection by any protocol | Partial — WebSocket only |
| 13 | Adaptive storage tiering | Not started |
| 14 | Redundant storage | Partial — local checkpoint + off-site backup script for chunks/manifests. Federation transport exists but content sync incomplete |
| 15 | Charge users at any rate | Not started |
| 16 | Handle any data type | Partial — 9 element types, no structured editing UI |
| 17 | Portable front-end | Partial — web SPA, no mobile |
Features from Ted Nelson's original vision that remain to be built.
| Feature | Status | What Exists |
|---|---|---|
| Live transclusion rendering | Shipped | Inline RangeElement::Transclusion in the O-tree CRDT with 32-level recursive resolution and cycle detection. Transcluded fragments render inline with live updates. |
| Compound documents | Shipped | Inline transclusion in the O-tree (single source of truth — no side-table drift). Compound tutorial exists. Span migration through arbitrary deltas. |
| Trails and guided paths | Shipped | TrailsPanel provides trail builder UI with create, reorder, remove, navigate, and publish. Curated document sequences with categories. |
| Inter-span links | Partial | Links at work-level only. No (work, edition, start, end) targeting |
| Three-way visual diff UI | Partial | Backend has three_way_diff() and three_way_merge(). No visual character-level diff UI |
| Full-text search | Partial | find_text_transcluders does fingerprint search. No general inverted-index search across all works |
| Feature | Status | What Exists |
|---|---|---|
| Royalty/micropayment system | Partial | StorageCost, CostMethod, cost() fully implemented. No payment flow or billing system |
| Adaptive storage tiering | Not started | All data on local filesystem. No hot/cold migration |
| Geographic replication | Partial | Crash-safe checkpoint + off-site backup script (backup-chunks.sh) for immutable chunks/manifests via rsync. Federation transport exists but content sync incomplete. Blobs and server keys excluded from backup — need separate handling. |
| Export / interchange format | Not started | No way to export works to a portable format |
| Version genealogy UI | Partial | API operations exist (version_ancestors, version_descendants). No UI |
| Non-destructive archive | Partial | WorkIrrevocablyUnpublish is destructive. No soft-delete or is_archived flag |
Arc<RwLock<Server>>, allowing concurrent reads (health checks, blob reads, session lookups) via with_server_ref. Gold was single-threaded with no concurrency support at all. The RwLock is a step up, but mutations (edits, saves) still acquire an exclusive write lock via with_server and are serialized — the bottleneck for write-heavy workloads.crdt_awareness_update accepts user_name from the client without server-side verification. Any connected user can impersonate any other user.Features present in the Udanax-Gold C++ codebase (~200,000 lines) that have no equivalent or only stubs in Xudanu.
| Feature | What Gold Had | What Xudanu Has |
|---|---|---|
| Tumbler / Sequence addressing | Full Sequence positions (1.2.3.4) with interval regions, prefix queries, edge types (INCLUSIVE, EXCLUSIVE, PREFIX) | Simple Vec<i64> — no hierarchical addressing or prefix queries |
| Multi-dimensional coordinate spaces | IntegerSpace, RealSpace, SequenceSpace, IDSpace, CrossSpace, FilterSpace with full mapping/ordering subsystems | Partial implementations in src/space/ — RealSpace and FilterSpace are stubs |
| CrossRegion endorsements | Endorsements as multi-dimensional CrossRegion over IDSpace × EndorsementSpace with projection and set operations. Per-region endorsements within editions. | Flat BTreeSet<Endorsement> — edition-wide only, no cross-space operations |
| Edition structural operations | combine(), replace(), stepper(Region, OrderSpec), isRangeIdentical(), makeRangeIdentical(), mapSharedOnto(), sharedWith() | with()/without() for single-position ops. three_way_diff() for text only. No structural Edition-level merge or identity unification. |
| PlaceHolder / FillDetector | PlaceHolder as a RangeElement for template slots. FillDetector fired async when slots were filled. Background transclusion resolution. | PlaceHolder in the enum but never created — no fill detection, no templates |
Recursive transclusion (again()) | RangeElement::again() returned the element this was a transclusion of, supporting recursive chains | No equivalent |
| IDHolder (in-content references) | IDHolder as a RangeElement wrapping an ID — editions could contain pointers to other objects inline | WorkRef/EditionRef variants exist but are not first-class RangeElements |
| Overlays | Compositional layers on editions with resolution pipeline | RangeElement::Overlay in enum — no overlay resolution or rendering |
| Feature | What Gold Had | What Xudanu Has |
|---|---|---|
| Recursive club membership | ClubDescription with Set of member clubs. Clubs could contain clubs with recursive resolution. | Flat clubs — no nesting, no delegation |
| Work sponsors | Separate sponsor relationship — clubs that vouched for a work's existence | No sponsor concept — only read/edit clubs |
| Work history club | Separate historyClub controlling access to revision history | History visible to anyone with read access |
| KeyMaster authority delegation | Accumulated authority from multiple logins, incorporate() to merge, removeLogins() to revoke | has_authority() checks only — no merge or delegation |
| ID batch allocation | IDSpace::global()/unique(), BatchCounter for preallocating ranges on disk, GrantTable for club-scoped allocation | Simple sequential u64 — no batch preallocation or grants |
| Feature | What Gold Had | What Xudanu Has |
|---|---|---|
| Link type sets and named ends | HyperLink with Set of link types, endNames(), MultiRef with set operations | Simple links — no type sets, no named ends, no MultiRef |
| Persistent agenda / task queue | Agenda/AgendaItem surviving crashes. Sequencer for ordered composition. Background transclusion resolution. | All operations synchronous — no deferred work, no crash-surviving task queue |
| Comm protocol negotiation | Portal for bidirectional channels, Negotiator for version negotiation, BootPlan for connection setup | Single WebSocket protocol — no version negotiation, no multi-transport |