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. Where the reader's PDS implements atproto permissioned data (proposal 0016), clients write these records into the reader's personal space instead — at://<did>/space/page.cavu.personal/self, an owner-only (empty member list) space anchored on the reader's own DID so their PDS is also the space host — and migrate any public record there on first read (merge, rewrite into the space, delete the public copy). Space records are invisible to backlink indexes, so private progress deliberately drops out of 'who is reading this book'. On any other PDS the record is a public repo record and clients must disclose at sign-in that reading positions are saved to the reader's own repo.
tid
Timestamp-based ID
Properties
finished
array
of
ref
com.atproto.repo.strongRef
Optional
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.
finishedAt
string
datetime
Optional
When the reader finished the book. Presence marks the book finished regardless of percent.
furthest
ref
#position
Optional
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.
percent
integer
Optional
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.
minimum: 0maximum: 100position
ref
#position
Required
Where the reader currently is — the resume point.
publication
string
at-uri
Required
AT-URI of the book's site.standard.publication record — the backlink target.
startedAt
string
datetime
Optional
When the reader started the book.
updatedAt
string
datetime
Required
When this record was last written. Doubles as last-read-at for surfacing recently read books.
View raw schema
{
"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. Where the reader's PDS implements atproto permissioned data (proposal 0016), clients write these records into the reader's personal space instead — at://<did>/space/page.cavu.personal/self, an owner-only (empty member list) space anchored on the reader's own DID so their PDS is also the space host — and migrate any public record there on first read (merge, rewrite into the space, delete the public copy). Space records are invisible to backlink indexes, so private progress deliberately drops out of 'who is reading this book'. On any other PDS the record is a public repo record and clients must disclose at sign-in that reading positions are saved to the reader's own repo."
}