How Xudanu stores documents, revisions, and images — and how they flow through the system to appear on your screen.
Five layers, each with a single responsibility. Data flows top to bottom on write, bottom to top on read.
useCrdtSync, spans in useCompoundEdition, images in imageEntries. Lost on page refresh.What's actually on disk. Three separate storage systems, each optimized for their data type.
How text gets from your keyboard to permanent disk, and how it comes back.
Figure 1: Document write flow (top) and read flow (bottom). Yellow = WAL, purple = server RAM, teal = disk, blue = on-demand revision loading.
Revisions exist at three levels of persistence. Each survives longer but costs more to access.
| Level | What's stored | Speed | Survives |
|---|---|---|---|
| 1. In-memory | Full Edition objects (revision_history BTreeMap) | Instant (HashMap lookup) | Until server restart |
| 2. Chunk store | Chunk references (WorkChunkRef.history map) | ~10ms (read + deserialize) | Forever (content-addressed) |
| 3. Manifest | Revision metadata (RevisionMeta: timestamps, descriptions) | Instant (loaded on startup) | Forever |
prev_chunk_history preserves old revision chunk references when mark_dirty() clears chunk_ref. Without this, editing a work after restart would lose all pre-restart revisions from the chunk store.
From drag-drop to rendered pixel. The image passes through 7 steps.
ArrayBuffer in the browser. MIME type detected from file extension./api/blob/upload with Content-Type header and session ID for auth. Not WebSocket — HTTP is 10× more efficient for binary.image crate), then:
RangeElement::Blob is inserted into the document's O-tree at the cursor position. This is how the image "knows" it belongs to this document at this position.Blob + blob: URL in the browser. Image appears instantly.How image data is structured at each layer.
The complete map of data in Xudanu.
| Data type | RAM (lost on restart) | Disk (permanent) | Access path |
|---|---|---|---|
| Document text | Works HashMap → Work → Edition | data/chunks/ (postcard format) | crdt_sync_open → text |
| Revisions (old editions) | Work.revision_history (current session only) | data/chunks/ (via WorkChunkRef.history) | work_text_at_revision() |
| Revision metadata | Server.revisions HashMap | manifest.json → revisions map | work_revisions_list() |
| Typed links | Server.links HashMap | manifest.json → links_hash → chunk | link_list_for_work() |
| Annotations (bold, italic, notes) | O-tree elements (part of edition) | data/chunks/ (part of edition) | annotation_list() |
| Images | — (not cached in RAM) | data/blobs/{hash} + data/blobs/{hash}_preview | GET /blobs/{hash} |
| Image reference in document | RangeElement::Blob (part of edition) | data/chunks/ (part of edition) | edition.all_entries() |
| Attribution (who wrote what) | — (queried on demand) | data/attribution/security.log.* | attribution_query_resolved() |
| Trails | Server.trails HashMap | manifest.json → social_chunk → chunk | trail_list() |
| Starred works | Server.starred_works HashMap | manifest.json → social_chunk → chunk | work_is_starred() |
work_blob_list wire op would scan the O-tree for RangeElement::Blob and return positions.
RangeElement::Overlay + ImageOverlay structs already exist. Would allow server-side crop/resize/rotate without duplicating the original image. The overlay references the base hash + list of operations.