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:

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:

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 ┘
  1. Resolve the DID to a data server

    The first segment is the author’s DID. Fetch its DID document — from https://plc.directory/{did} for did:plc, or https://{host}/.well-known/did.json for did:web — and read the #atproto_pds service entry. Its endpoint is the PDS that hosts everything else.

  2. 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.

  3. Find the book object

    Look for a book key 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.

  4. Walk the spine

    Each spine entry’s document AT-URI resolves with the same getRecord call (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.

  5. Fetch the table of contents

    Take the spine entry whose roles include toc, fetch that one document, and scan its blocks for the page.cavu.blocks.toc block. Publication plus TOC document — two reads — gives full navigation for the whole book.

  6. Render the content

    Each document’s content field is an open union. page.cavu.content is text: a flat array of blocks — paragraphs, headers, block quotes, images, verse — with byte-range facets for bold, italics, and links. page.cavu.pages is 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