Documentation
How a book gets here
Every book you read on Cavu lives in its author’s own data repository on the AT Protocol. This reader has no database — it resolves each book, record by record, straight from the author’s server. Here is how.
Books are made of records
The AT Protocol stores data as records in per-user repositories, each hosted on a Personal Data Server (PDS) the user controls. Records have a hard size ceiling, and a novel doesn’t fit in one — so a book is split across several:
- One publication record —
site.standard.publication, from the standard.site lexicons. This is the book’s front door: title, description, and apage.cavu.bookextension object carrying everything a reader needs to assemble the book. - Several content documents —
site.standard.documentrecords, each holding a chunk of the book: usually a chapter, sometimes several, sometimes part of one. Chunks are storage units sized for network limits, not units of meaning.
That last distinction matters. A chapter is where the table of contents says it starts — a point inside some record — not a record boundary. Reading software must be able to flow across chunk edges as if they weren’t there.
The spine
The spine is the ordered list of content documents that makes those chunks a book. It lives on the publication record, inside the page.cavu.book object, and it borrows its name and its semantics from EPUB: the spine defines the continuous reading order, and nothing else.
{
"$type": "site.standard.publication",
"name": "The Driftwood Library",
"url": "https://cavu.page/did:plc:…/3abc…",
"book": {
"$type": "page.cavu.book",
"spine": [
{ "document": "at://did:plc:…/site.standard.document/3k2f…",
"roles": ["frontmatter", "toc"] },
{ "document": "at://did:plc:…/site.standard.document/3k2g…" },
{ "document": "at://did:plc:…/site.standard.document/3k2h…" },
{ "document": "at://did:plc:…/site.standard.document/3k2j…",
"roles": ["appendix"], "linear": false }
],
"completionStatus": "complete"
}
}Each spine entry is a small property bag:
document— the AT-URI of asite.standard.documentchunk, almost always in the same repository as the publication.roles— optional structural semantics, using EPUB’s vocabulary.tocmarks the document that carries the table of contents;frontmattermarks the title-page material.linear—falsemarks supplementary material (appendices, notes) skipped in continuous reading, exactly as in EPUB. Absent meanstrue.
Spine versus table of contents
The spine says what order the chunks flow in. The table of contents says where the chapters are — and it is not stored on the publication at all. Following EPUB 3’s navigation-document design, the TOC is a content block (page.cavu.blocks.toc) inside one of the spine documents, so it renders as a contents page at its natural place in the book while staying machine-readable. Its entries are flat and depth-annotated — parts, chapters, subchapters — and each leaf entry targets a (document AT-URI, block id) pair: a chapter can begin anywhere inside any chunk.
Inside the text itself, page.cavu.blocks.chapterTitle blocks mark chapter openings explicitly, so renderers never infer structure from record boundaries or from headings.
Resolving a book, step by step
Everything below uses unauthenticated, public XRPC calls — any client can do this. Start from a publication AT-URI:
at://did:plc:abc123…/site.standard.publication/3lxyz…
└── repository ┘ └───── collection ─────┘ └ rkey ┘Resolve the DID to a data server
The first segment is the author’s DID. Fetch its DID document — from
https://plc.directory/{did}fordid:plc, orhttps://{host}/.well-known/did.jsonfordid:web— and read the#atproto_pdsservice entry. Its endpoint is the PDS that hosts everything else.Fetch the publication record
GET {pds}/xrpc/com.atproto.repo.getRecord ?repo={did} &collection=site.standard.publication &rkey={rkey}The response envelope carries the record’s current CID and its value: the publication.
Find the book object
Look for a
bookkey whose value is tagged"$type": "page.cavu.book". Its presence is what makes this publication a book — a publication without it might be a series, a blog, or someone else’s thing entirely. The standard fields (name,description,icon) stay populated so generic standard.site apps can still show a sensible card.Walk the spine
Each spine entry’s
documentAT-URI resolves with the samegetRecordcall (the DID is usually the same, so the PDS is already known). A reader doesn’t need them all at once: this one keeps a three-chunk window — previous, current, next — and fetches the rest as you read.Fetch the table of contents
Take the spine entry whose
rolesincludetoc, fetch that one document, and scan its blocks for thepage.cavu.blocks.tocblock. Publication plus TOC document — two reads — gives full navigation for the whole book.Render the content
Each document’s
contentfield is an open union.page.cavu.contentis text: a flat array of blocks — paragraphs, headers, block quotes, images, verse — with byte-range facets for bold, italics, and links.page.cavu.pagesis pre-paginated comic content: an ordered list of image pages with spread hints and reading direction. The two mix freely in one spine.Images and covers are blobs, fetched from the same PDS:
GET {pds}/xrpc/com.atproto.sync.getBlob?did={did}&cid={cid}
That’s the whole pipeline: one DID resolution, one record fetch to know everything about the book’s shape, and further fetches only for the chunks you actually read. No index, no backend, no permission needed — the book is wherever its author put it.
Why it’s built this way
- Authors own their books. The records live in the author’s repository on a server they choose. This reader is one renderer among any number possible; if it disappeared tomorrow, the books would not.
- Serialization is native. Publishing a new chapter is one atomic batch: add chunk documents, update the TOC, update the spine. Subscribers (via
site.standard.graph.subscription) see a consistent book at every moment. - The publication record stays small. Spine entries are lean — titles live in the TOC document, content in the chunks — so even a thousand-chapter serial’s front door is a quick fetch for every subscriber and indexer.