{
"id": "page.cavu.progress",
"defs": {
"main": {
"key": "tid",
"type": "record",
"record": {
"type": "object",
"required": [
"publication",
"position",
"updatedAt"
],
"properties": {
"percent": {
"type": "integer",
"maximum": 100,
"minimum": 0,
"description": "Denormalized whole-book completion percent at `position`, weighted over body-matter chapters, computed by the writing client (which has the book loaded). Lets list views render progress bars from this record plus the publication record alone. A serial's denominator grows with each release, so display against the live spine when possible."
},
"finished": {
"type": "array",
"items": {
"ref": "com.atproto.repo.strongRef",
"type": "ref"
},
"description": "Spine documents read to completion, each pinned at the cid it had when finished. A finished entry whose cid no longer matches the live record marks a chunk that changed after the reader read it; re-reading a changed chunk to completion replaces its entry with the new cid."
},
"furthest": {
"ref": "#position",
"type": "ref",
"description": "Furthest position ever reached, when it differs from position (re-reading, or a device syncing an older current position). Clients max-merge by spine order: position is last-writer-wins, furthest never regresses."
},
"position": {
"ref": "#position",
"type": "ref",
"description": "Where the reader currently is — the resume point."
},
"startedAt": {
"type": "string",
"format": "datetime",
"description": "When the reader started the book."
},
"updatedAt": {
"type": "string",
"format": "datetime",
"description": "When this record was last written. Doubles as last-read-at for surfacing recently read books."
},
"finishedAt": {
"type": "string",
"format": "datetime",
"description": "When the reader finished the book. Presence marks the book finished regardless of percent."
},
"publication": {
"type": "string",
"format": "at-uri",
"description": "AT-URI of the book's site.standard.publication record — the backlink target."
}
}
},
"description": "Reading progress for one book, written to the reader's own repo. One record per (reader, publication) by convention: clients list their own repo and update the existing record for a publication rather than create a second (the BookHive shelf pattern). The publication AT-URI makes records discoverable by backlink index (Microcosm/Constellation), so 'who is reading this book' needs no app-side database. Positions anchor documents by strongRef: the cid pins the chunk *as read*, so a later edit to an already-read chunk is detectable by cid drift and clients can flag it. Repo records are currently public: clients must disclose at sign-in that reading positions are saved to the reader's own repo. These records are intended to move into permissioned spaces once that capability is live."
},
"position": {
"type": "object",
"required": [
"document"
],
"properties": {
"document": {
"ref": "com.atproto.repo.strongRef",
"type": "ref",
"description": "Spine document the reader is in, at the cid last seen by this client."
},
"thousandths": {
"type": "integer",
"maximum": 1000,
"minimum": 0,
"description": "How far through the document the reader is, in thousandths (0 = top, 1000 = end). Omit when unknown; clients treat absence as the top of the document."
}
},
"description": "A place in a book: the spine document (pinned at its as-seen cid) plus how far through it the reader is. Fractional depth in thousandths keeps the grammar integer-only and is deliberately edit-tolerant — after an in-place edit it lands near the old spot and clamps, and the strongRef cid tells clients the ground shifted."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}