From fc5de51a0a5451b1f59db032dd132229b77bf1b2 Mon Sep 17 00:00:00 2001 From: KodeStar Date: Fri, 13 Nov 2026 11:40:38 +0000 Subject: [PATCH 1/7] Document LibriVox links and /meta answering from the recorded match The book page's About card now ends with "Also free on LibriVox" when the book's matched recording is a LibriVox one, and /meta's recording carries an additive librivox {id, url?, zip_url?} block (http(s) links only, never built from the id). /meta also answers a book its ASIN or ISBN don't match from its recorded console match (book_matches work_id + recording_id), cached under its own key space. Updates the user guide's book page and the lookup privacy lines, the /meta reference (resolution order, the field, the 500 for an unreadable match), the cache keys in configuration and the data model, the cross-repo contract, the console's series-gap note, and the player's About and wire-field notes. The player still asks /meta only for a book with an ASIN or ISBN or generated content, and the docs say so. --- .../architecture/cross-repo-contract.md | 17 ++++-- docs-developers/frontend/book-page.md | 11 ++++ docs-developers/frontend/state-and-data.md | 3 + docs-developers/server/api/reference.md | 58 +++++++++++++++---- docs-developers/server/configuration.md | 16 +++-- docs-developers/server/data-model.md | 17 ++++-- docs-developers/server/web-ui.md | 5 +- docs-users/admin/server.md | 4 +- docs-users/faq.md | 2 +- docs-users/listening/book-page.md | 2 + 10 files changed, 104 insertions(+), 31 deletions(-) diff --git a/docs-developers/architecture/cross-repo-contract.md b/docs-developers/architecture/cross-repo-contract.md index 3a5495f2..fd79f020 100644 --- a/docs-developers/architecture/cross-repo-contract.md +++ b/docs-developers/architecture/cross-repo-contract.md @@ -760,7 +760,8 @@ player. **Server:** - `internal/meta` resolves a book's ASIN or ISBN against `metaserve` and composes an - enrichment envelope. `GET /libraries/{id}/meta` returns it, behind an admin + enrichment envelope. A book with neither, or one `metaserve` does not know, is + resolved by its recorded match instead (see [Matches by work id](#matches-by-work-id)). `GET /libraries/{id}/meta` returns it, behind an admin off-switch and a bounded cache. - `GET /meta/work?id=…` serves one work document straight through, from the same cache, for the earlier books of a series the caller does not own. It is authed but @@ -864,9 +865,14 @@ From 2.6.0 the server records each match by its work id (`book_matches`). So a s listing that `metaserve` holds with no ASIN or ISBN counts as matched. A refresh looks such a book up by `works/{id}`, following a retired slug's `301`. The rule a run applies to such a book is in -[Bulk community matching](../server/api/reference.md#bulk-community-matching). The -player's `/meta` stays identifier-keyed, so it shows no community content for such a -book yet. +[Bulk community matching](../server/api/reference.md#bulk-community-matching). + +From 2.7.0 the server's `/meta` answers such a book from its recorded match: the +book's identifiers first, then its `book_matches` `work_id` and `recording_id` +when they find nothing. The envelope is the same, so no client changes for it. +The player still asks `/meta` only for a book with an ASIN or ISBN, or with +generated content (`metaEnabledFor`), so a book matched with neither and with no +generated content shows no community content in the player. ### Community chapters from the meta side @@ -934,6 +940,9 @@ These are all additive: writes the legal text.** A client must render it beside that content and never compose it. - `recording.chapter_count`. +- `recording.librivox` `{id, url?, zip_url?}`, only on a LibriVox recording. The + server keeps each URL only when it is an absolute http(s) URL and never builds + one from `id`. The player checks `url` again before About links it. - `local` `{library_id, path}` on each rail entry the caller owns, in the main view and the orderings. The server resolves `local` **per request, after the shared cache, on a copy**, and never stores it there. A failure placing the caller's books diff --git a/docs-developers/frontend/book-page.md b/docs-developers/frontend/book-page.md index d35edff9..1c1ab13f 100644 --- a/docs-developers/frontend/book-page.md +++ b/docs-developers/frontend/book-page.md @@ -403,6 +403,17 @@ hero and the tabs. (`openLibraryRoot`) on `libraryBooksHref({ genre | tag })`. - A matched book without community text keeps "View on AudioSilo Meta". Long text folds past six lines with Show more. + - A LibriVox recording adds "Also free on LibriVox" (`book.about.librivox`) under + the other links out: after the credit and **Improve this**, else after "View on + AudioSilo Meta". It opens `librivoxUrl(recording)` through `openExternalUrl`. + `librivoxUrl` (`book-details-model.ts`) reads the matched recording's + `librivox.url` and keeps it only when it is an absolute http(s) URL with a host + (`urlEndpoint`). It lower-cases the scheme, because the in-app browser on iOS + (`expo-web-browser`) refuses `HTTPS://` and `openExternalUrl` swallows the + error. The server checks the URL too; this is the second check, kept because + the link leaves the app. The envelope carries one recording, the book's own + match, so there is no list to pick from. With no `librivox.url`, or on an + older server, there is no link. - **Other versions** - `BookVersions`: every other copy (`useBookCopies`) with where it lives and its quality, each opening that copy's page. Nothing shows when there is none. diff --git a/docs-developers/frontend/state-and-data.md b/docs-developers/frontend/state-and-data.md index b8b23d73..f9b75cf2 100644 --- a/docs-developers/frontend/state-and-data.md +++ b/docs-developers/frontend/state-and-data.md @@ -614,6 +614,9 @@ older server (React Query rejects it instead), and the query stays pending. it; the series page and Search's character group do. `community_description` feeds the book page's About card (`aboutContent`). - `BookMetaRecording.chapter_count`, which has no reader yet. + - `BookMetaRecording.librivox` (`BookMetaLibrivox { id, url?, zip_url? }`), only + on a LibriVox recording. The About card links `url` + ([The aside](book-page.md#the-aside)); nothing reads `id` or `zip_url` yet. - `local` (`BookRef`) on rail entries. It is how the series page and Next in your series tell an owned book from a ghost. - `previous` on a matched `BookMeta`. The diff --git a/docs-developers/server/api/reference.md b/docs-developers/server/api/reference.md index 88c95f67..35748b37 100644 --- a/docs-developers/server/api/reference.md +++ b/docs-developers/server/api/reference.md @@ -2421,12 +2421,25 @@ path is authorized against the caller's share scope exactly like `/item`. The ro is gated by the `metadata` [capability](#get-apiv1server). When it is false the route returns 404, so a client that honours the flag never calls this. -The server resolves the book's `asin` or `isbn`. These are the effective fields on -the indexed book: stored in `book_enrichment`, or set by an admin edit, which wins. -It looks the recording up upstream and folds the matched recording plus up to three +The server finds the book's work in two steps (`bookEnvelope`): + +1. **Its identifiers.** The server looks the book's `asin` or `isbn` up upstream. + These are the effective fields on the indexed book: stored in + `book_enrichment`, or set by an admin edit, which wins. A book its identifiers + match never reads its recorded match. +2. **Its recorded match.** When the book has neither identifier, or the upstream + does not know them, the server reads the book's recorded community match. That + is its `book_matches` row, shown as + [`community_match`](#get-apiv1adminlibrariesidbook) on the console's book page: + the `work_id` and `recording_id` an admin accepted or a match run applied. The + server fetches that work (`works/{id}`, `meta.Service.EnrichMatch`) and makes + the recording the match names the envelope's recording, as it would a recording + a lookup named. A book with no match row is unmatched. + +Either way the server folds the matched recording plus up to three series rails into one envelope, one rail per reading-order family (see [Reading-order families](#reading-order-families)). Every series-rail entry carries its own `web_url`, so a client links -to the metadata site without ever building a URL itself. Answers are cached in +to the metadata site without ever building a URL itself. Answers are cached, by identifier or by the recorded match, in memory and in the server's database (see [Configuration](../configuration.md#community-metadata-metadata)), so a restart serves them warm and a known book keeps its enrichment through a metadata-service @@ -2519,10 +2532,31 @@ Response `200` on a match: `work` is always present on a match. `recording` and `series` are omitted when the upstream has none. Every `omitempty` string or number field (`subtitle`, `first_published`, `description`, `abridged`, `runtime_min`, `release_date`, -`publisher`, `cover_url`, `chapter_count`) is dropped when empty. +`publisher`, `cover_url`, `chapter_count`) is dropped when empty, and so is +`recording.librivox`. - `recording.chapter_count` is the matched recording's chapter count, omitted when the metadata service does not know it. +- `recording.librivox` (2.7.0, additive) says where a LibriVox recording is + published. It is present only when the community gives the recording a LibriVox + project id (`librivox_id` above 0), and absent on every other recording and on + older servers: + + ```json + "librivox": { + "id": 47, + "url": "https://librivox.org/…", + "zip_url": "https://archive.org/compress/…" + } + ``` + + `id` is the LibriVox project id. `url` is the recording's page on librivox.org + and `zip_url` the whole recording as one zip, from the community's + `links.librivox` and `links.zip`. Each is optional, and the server keeps it only + when it is an absolute `http` or `https` URL with a host (`librivoxOf`). It + never builds either from `id`, because LibriVox's pages are named by slug. A + cached envelope gains the field once its entry is next refreshed. The player + links `url` from About and does not use `zip_url`. - `series[].position` is this work's position in that series. Positions are strings ("1", "2.5", "1-3.5"). - `series[].works` is the full ordered rail, **including the current work**. The @@ -2822,8 +2856,8 @@ this param: it gates on the device against its live position, which can be ahead of the last saved one. `spoilers=hide` is for clients that cannot gate themselves. -When the book has neither an `asin` nor an `isbn`, or the upstream reports no -match, the response is `200 { "matched": false }`. That is a normal, non-error result +When neither the book's identifiers nor its recorded match (if any) find a work +upstream, the response is `200 { "matched": false }`. That is a normal, non-error result that the client treats as "nothing to show": ```json @@ -2869,7 +2903,8 @@ content wins **per dimension**: `sidecars.describe` job shows its own time. - The whole object is omitted when nothing was generated, or the community has it all. -- **A book with no match** (no `asin`/`isbn`, or none upstream) but generated +- **A book with no match** (neither its identifiers nor its recorded match find + a work) but generated content answers `matched: false` **with a work** of its own: `id` empty, `title` the book's, `authors` `[]`, the generated `characters`, `recaps`, `recap_summary` and `generated_description`, and `generated`: @@ -2937,7 +2972,7 @@ generated description, because it is a field they don't know. | `400` | missing `path` / invalid library id | | `403` | path outside the caller's share scope | | `404` | `no book at that path`, **or** metadata lookup is disabled on this server (`metadata` capability false) | -| `500` | with `spoilers=hide`, `could not load progress` - the caller's progress could not be read; the ungated envelope is never sent instead | +| `500` | `could not load book` - the book's recorded match could not be read. With `spoilers=hide`, also `could not load progress` - the caller's progress could not be read; the ungated envelope is never sent instead | | `502` | `metadata service unavailable` - the upstream was unreachable or errored, the server holds no earlier answer for the book, and it has no generated content | ### `GET /api/v1/libraries/{id}/meta/cover` @@ -6421,8 +6456,9 @@ is a `400` too (`too many format or codec values`). duplicates groups' copies and the book page's `book` carry it too. - `matched` - the book has an ASIN or ISBN, or a recorded community match (a store listing with neither, from 2.6.0): the same rule as the `matched` filter - (`catalog.matchedExpr`). The player's `/meta` and the community chapter check find - a book by its ASIN or ISBN alone, so check `asin` / `isbn` for those, not `matched`. + (`catalog.matchedExpr`). The community chapter check finds a book by its ASIN or + ISBN alone, and the player asks `/meta` only for a book with one (or with generated + content), so check `asin` / `isbn` for those, not `matched`. - `abridged` - `true`, `false`, or `null` while nothing says (always present, unlike the player's `Book`, which omits it). `genres` and `tags` are `[]` for none. diff --git a/docs-developers/server/configuration.md b/docs-developers/server/configuration.md index a6cebb21..235cbec4 100644 --- a/docs-developers/server/configuration.md +++ b/docs-developers/server/configuration.md @@ -422,7 +422,8 @@ saving (see [`GET /admin/settings`](api/reference.md#get-apiv1adminsettings)). ### Community metadata (`metadata.*`) The server can enrich a book with a description, production details and its -series. It resolves the book's ASIN or ISBN against the community metadata API +series. It resolves the book's ASIN or ISBN, else the book's recorded community +match, against the community metadata API ([meta.audiosilo.app](https://meta.audiosilo.app)) and serves the result at `GET /libraries/{id}/meta`. Players draw it as an "About this book" block plus the recaps, characters and series tabs. The same config also gates @@ -681,14 +682,17 @@ database, in the `meta_cache` table (migration `0024`; see makes the cache survive a restart and an outage: - **What it holds:** - - A book's enrichment, keyed by its ASIN or ISBN: a match, a "no match", or an - envelope missing a series rail. An envelope missing a rail is kept for 2 - minutes only, and never over a stored answer, which stays the outage fallback. + - A book's enrichment, keyed by its ASIN or ISBN, or by its recorded community + match's work and recording ids for a book its identifiers don't match: a + match, a "no match", or an envelope missing a series rail. An envelope + missing a rail is kept for 2 minutes only, and never over a stored answer, + which stays the outage fallback. - Works fetched by id (`/meta/work`, and the previous books `/meta` adds), matches only. - It never holds a service error, and never a row for an unknown work id. An id - is the caller's choice, so storing misses would let any user grow the table. A + It never holds a service error, and never a row for a work id that `/meta/work` + or `previous` found unknown. That id is the caller's choice, so storing misses + would let any user grow the table. A `404` from the service for a work already stored replaces its row, so a dropped work isn't served again in a later outage. In mirror mode a `404` the local copy gave never does (see [Mirror mode](#mirror-mode-metadatamode-mirror)). diff --git a/docs-developers/server/data-model.md b/docs-developers/server/data-model.md index 5f1eeb56..85a74c8d 100644 --- a/docs-developers/server/data-model.md +++ b/docs-developers/server/data-model.md @@ -1621,8 +1621,9 @@ metadata cache (`internal/meta.Store`, `catalog/metacache.go`). It holds the answers the in-memory cache holds, so a restart serves them warm, and a known book keeps its enrichment through a metadata-service outage. Columns: -- `key`: the primary key. A book's enrichment is `a:` or `i:`, and - a work fetched by id is `w:`. +- `key`: the primary key. A book's enrichment is `a:` or `i:`, the + enrichment of a recorded community match is `m:/` (each + id path-escaped), and a work fetched by id is `w:`. - `version`: the payload's format. A row of another version is ignored. - `source`: the metadata service's base URL the answer came from. A row from another URL is ignored. @@ -1631,7 +1632,8 @@ book keeps its enrichment through a metadata-service outage. Columns: Index `idx_meta_cache_stored` on `stored_at`. -It holds community data only, keyed by identifier, never by a book or a user. +It holds community data only, keyed by identifier or community id, never by a +book or a user. Nothing moves it with a book or purges it with an account, and dropping it costs one upstream lookup per book. @@ -1834,8 +1836,13 @@ this row alone. `catalog.matchedExpr` - a refresh run's `MatchedBooks`. What needs an identifier tests `asin` or `isbn` itself: community chapters, the -unknown-ASIN report, upcoming releases, a repick, the Bridge's estimate and the -player's `/meta`. +unknown-ASIN report, upcoming releases, a repick and the Bridge's estimate. + +`/meta` reads the row only when the book's identifiers find nothing: a book with +neither, or one the upstream does not know, answers from its recorded match +(see [`GET /libraries/{id}/meta`](api/reference.md#get-apiv1librariesidmeta)). +The player asks `/meta` only for a book with an ASIN or ISBN, or with generated +content. The row moves with the book (`moveBookState`, as one set with its edits), is copied from the first part on a join, is kept through a rescan or rebuild, and diff --git a/docs-developers/server/web-ui.md b/docs-developers/server/web-ui.md index b22c44b0..a256c095 100644 --- a/docs-developers/server/web-ui.md +++ b/docs-developers/server/web-ui.md @@ -556,8 +556,9 @@ Undo. metadata is off: - The gaps come from the `GET /libraries/{id}/meta` of each series' first book - with an ASIN or ISBN (`metaCandidate`, `hasIdentifier`), because `/meta` is - identifier-keyed. + with an ASIN or ISBN (`metaCandidate`, `hasIdentifier`). A book matched with + neither is never the one asked, although `/meta` would answer it from its + recorded match. - The card offers "Match a book" when that `/meta` found no match. When no book has an ASIN or ISBN, it offers it when none of the series is matched. - Each card resolves its books with an ASIN or ISBN to community work ids diff --git a/docs-users/admin/server.md b/docs-users/admin/server.md index c6808dfa..71abbff2 100644 --- a/docs-users/admin/server.md +++ b/docs-users/admin/server.md @@ -140,8 +140,8 @@ description, series, and (where written) characters and recaps. See What is sent: -- **Looking up a book** sends only its ASIN or ISBN, never file paths or who - is listening. +- **Looking up a book** sends only its ASIN or ISBN, or the community entry + you matched it to, never file paths or who is listening. - **Matching a book** in the console sends search terms from its tags, folder names and length, only when you match. See [What matching sends](books.md#what-matching-sends). diff --git a/docs-users/faq.md b/docs-users/faq.md index c7ca5488..4f3c1f98 100644 --- a/docs-users/faq.md +++ b/docs-users/faq.md @@ -65,7 +65,7 @@ Three names come up across these docs: Only what a lookup needs: -- **Community metadata** sends a book's ASIN or ISBN to meta.audiosilo.app. Matching a book also sends details like its title and author (see [Server settings](./admin/server.md#community-metadata)). The server never sends listener details or full file paths. A match can send up to three names from the book's path (its author, series and book folders) as search terms. +- **Community metadata** sends meta.audiosilo.app a book's ASIN or ISBN, or the community entry an admin matched it to. Matching a book also sends details like its title and author (see [Server settings](./admin/server.md#community-metadata)). The server never sends listener details or full file paths. A match can send up to three names from the book's path (its author, series and book folders) as search terms. - **Help complete the community database** (off by default) sends, once a day, the ASINs the database doesn't know. See [Helping complete the community database](./admin/server.md#helping-complete-the-community-database). - **Podcast search** sends the search text to Apple Podcasts or Podcast Index. See [Searching podcast directories](./admin/podcasts.md#searching-podcast-directories). - **The update check** asks GitHub once a day for the newest version. See [What the update check sends](./admin/server.md#what-the-update-check-sends). diff --git a/docs-users/listening/book-page.md b/docs-users/listening/book-page.md index 1c0312de..07edf73a 100644 --- a/docs-users/listening/book-page.md +++ b/docs-users/listening/book-page.md @@ -57,6 +57,8 @@ If neither has one, your server's [Bridge](../admin/generating-characters-and-re Under the description you'll find the publisher, release date, language and whether it's abridged, when known. Then come the book's **Genres** and **Tags**. Tap one to open the [Library](browsing.md#books) filtered to it. +When the community database matches the book to a free LibriVox recording, the card ends with **Also free on LibriVox**. It opens that recording's page on librivox.org. + Below the About card: - **Other versions** lists copies of the same book in your other libraries or servers. From f5feb368496711cb0a0247364f5afb64feb666b5 Mon Sep 17 00:00:00 2001 From: KodeStar Date: Fri, 13 Nov 2026 12:00:38 +0000 Subject: [PATCH 2/7] Document the book page asking /meta for a console-matched book The player's book page now asks /meta for every loaded non-episode book (bookPageMetaEnabled), so a book matched in the console with no ASIN or ISBN shows its community About, tabs and LibriVox link there. The companion, reveal toast, Home's Now card and Previously on, the end credits and offline copies keep the identifier gate (metaEnabledFor, canMatch). Updates the user guide (book page, recaps and characters, admin books and server settings) and the developer pages that said the player only asks for a book with an identifier. --- .../architecture/cross-repo-contract.md | 13 +++++++++---- docs-developers/frontend/offline.md | 8 +++++--- docs-developers/frontend/state-and-data.md | 17 +++++++++++++++-- docs-developers/server/api/reference.md | 4 ++-- docs-developers/server/data-model.md | 4 ++-- docs-users/admin/books.md | 6 ++++-- docs-users/admin/server.md | 5 +++-- docs-users/listening/book-page.md | 2 +- docs-users/listening/recaps-and-characters.md | 3 ++- 9 files changed, 43 insertions(+), 19 deletions(-) diff --git a/docs-developers/architecture/cross-repo-contract.md b/docs-developers/architecture/cross-repo-contract.md index fd79f020..2dcda751 100644 --- a/docs-developers/architecture/cross-repo-contract.md +++ b/docs-developers/architecture/cross-repo-contract.md @@ -869,10 +869,15 @@ run applies to such a book is in From 2.7.0 the server's `/meta` answers such a book from its recorded match: the book's identifiers first, then its `book_matches` `work_id` and `recording_id` -when they find nothing. The envelope is the same, so no client changes for it. -The player still asks `/meta` only for a book with an ASIN or ISBN, or with -generated content (`metaEnabledFor`), so a book matched with neither and with no -generated content shows no community content in the player. +when they find nothing. The envelope is the same, so the wire does not change. + +The player's book page asks `/meta` for every loaded book that is not an episode +(`bookPageMetaEnabled`), so a book matched with neither identifier shows its +community content there. Search's characters ask the listener's started books +with or without identifiers (`characterBooksToLoad`). Every other surface keeps +the identifier gate (`metaEnabledFor`: an ASIN, an ISBN or generated content): +the companion and reveal toast, Home's Now card and Previously on, the end +credits, and the offline copy (`canMatch`). ### Community chapters from the meta side diff --git a/docs-developers/frontend/offline.md b/docs-developers/frontend/offline.md index bac787bf..67bf9746 100644 --- a/docs-developers/frontend/offline.md +++ b/docs-developers/frontend/offline.md @@ -989,9 +989,11 @@ a marker (`DownloadManifest.meta = { savedAt }`). write the registry once (`persistSoon`). If the download was removed or replaced while the file was written, the marker isn't set, and a removed book's file is deleted again. -- *What `captureOfflineMeta` skips.* A book that can't match because it has no ASIN - or ISBN (`canMatch`, which reads the cached full item first, since the manifest's - book can be the list shape). Also a server without `metadata`. +- *What `captureOfflineMeta` skips.* A book with no ASIN or ISBN and no generated + content (`canMatch`, `metaEnabledFor` on the cached full item first, since the + manifest's book can be the list shape). That includes a book matched only in the + console: online its page shows the match, offline it reads as unmatched. Also a + server without `metadata`. - *How it reads.* It reads through the screens' own option factories (`bookMetaQuery`, `metaWorkQuery`), so it reuses a fresh answer they hold. It never throws and never retries. diff --git a/docs-developers/frontend/state-and-data.md b/docs-developers/frontend/state-and-data.md index f9b75cf2..e21fe6a4 100644 --- a/docs-developers/frontend/state-and-data.md +++ b/docs-developers/frontend/state-and-data.md @@ -986,8 +986,21 @@ Each field is read through one of two readers, never both: [generated work](../server/api/reference.md#generated-content) (`matched: false` with a `work`). So an unmatched book's generated description reaches About too. -`metaEnabledFor` asks `/meta` for a book with an ASIN or ISBN **or** -`generated: true`. An answer that is only generated content goes stale after +Two gates in `meta-gating.ts` decide whether a screen asks `/meta`: + +- `bookPageMetaEnabled`: the `metadata` capability and a loaded book that is not + an episode, identifiers or not. Only the book page uses it. A newer server + answers a book with no ASIN or ISBN from its console match (a LibriVox + download), and an older one answers `matched: false` without asking upstream, + so the one request is cheap either way. +- `metaEnabledFor`: that gate and an ASIN, an ISBN **or** `generated: true`. The + companion and reveal toast, Home's Now card and Previously on, the end credits + and the offline copy (`canMatch` in `offline-meta.ts`) use it, because they ask + for a book in passing or for every download. + +The series page's anchor (`metadataAnchor`, an ASIN or ISBN) and Search's +characters (`characterBooksToLoad`, the started books, ids or not) pick their +books by their own rules. An answer that is only generated content goes stale after 2 minutes (`META_GENERATED_ONLY_STALE_MS`), rather than the hour a community answer keeps. diff --git a/docs-developers/server/api/reference.md b/docs-developers/server/api/reference.md index 35748b37..43fc55b5 100644 --- a/docs-developers/server/api/reference.md +++ b/docs-developers/server/api/reference.md @@ -6457,8 +6457,8 @@ is a `400` too (`too many format or codec values`). - `matched` - the book has an ASIN or ISBN, or a recorded community match (a store listing with neither, from 2.6.0): the same rule as the `matched` filter (`catalog.matchedExpr`). The community chapter check finds a book by its ASIN or - ISBN alone, and the player asks `/meta` only for a book with one (or with generated - content), so check `asin` / `isbn` for those, not `matched`. + ISBN alone, so check `asin` / `isbn` for it, not `matched`. `/meta` answers a + `matched` book either way. - `abridged` - `true`, `false`, or `null` while nothing says (always present, unlike the player's `Book`, which omits it). `genres` and `tags` are `[]` for none. diff --git a/docs-developers/server/data-model.md b/docs-developers/server/data-model.md index 85a74c8d..60ffe97c 100644 --- a/docs-developers/server/data-model.md +++ b/docs-developers/server/data-model.md @@ -1841,8 +1841,8 @@ unknown-ASIN report, upcoming releases, a repick and the Bridge's estimate. `/meta` reads the row only when the book's identifiers find nothing: a book with neither, or one the upstream does not know, answers from its recorded match (see [`GET /libraries/{id}/meta`](api/reference.md#get-apiv1librariesidmeta)). -The player asks `/meta` only for a book with an ASIN or ISBN, or with generated -content. +The player's book page asks `/meta` for any book, but its other surfaces and the +offline copy ask only for a book with an ASIN or ISBN, or with generated content. The row moves with the book (`moveBookState`, as one set with its edits), is copied from the first part on a join, is kept through a rescan or rebuild, and diff --git a/docs-users/admin/books.md b/docs-users/admin/books.md index 9ccd6ef6..4faea0fb 100644 --- a/docs-users/admin/books.md +++ b/docs-users/admin/books.md @@ -87,8 +87,10 @@ Its **Community metadata** card says "No ASIN or ISBN: matched to its store listing". A few things need an ASIN or ISBN, so they leave such a book out: the -[community chapter check](#community-chapters), the missing books on the -[Series](#series) page, and the players' community "About this book". +[community chapter check](#community-chapters) and the missing books on the +[Series](#series) page. In the players, such a book shows its community details +on its book page only. The companion, Home's cards, the end credits and +downloaded copies leave them out. ### Unlinking a community match diff --git a/docs-users/admin/server.md b/docs-users/admin/server.md index 71abbff2..0ef73b6e 100644 --- a/docs-users/admin/server.md +++ b/docs-users/admin/server.md @@ -134,8 +134,9 @@ servers leave it empty. **Look up community metadata** turns on extra book details from the free community database at [AudioSilo Meta](https://meta.audiosilo.app). Books -with an ASIN or ISBN gain an "About this book" section in the player: a -description, series, and (where written) characters and recaps. See +with an ASIN or ISBN, and books you matched in the console, gain an "About this +book" section in the player: a description, series, and (where written) +characters and recaps. See [A book's page](../listening/book-page.md#about-this-book). What is sent: diff --git a/docs-users/listening/book-page.md b/docs-users/listening/book-page.md index 07edf73a..7d595835 100644 --- a/docs-users/listening/book-page.md +++ b/docs-users/listening/book-page.md @@ -49,7 +49,7 @@ A finished book shows **Finished** with the date. If your server keeps ratings, ## About this book -The **About** card describes the book. The description comes from the community database, [AudioSilo Meta](../community/meta-site.md), when your server has that switched on and the book is matched. Otherwise it's the one your server holds. +The **About** card describes the book. The description comes from the community database, [AudioSilo Meta](../community/meta-site.md), when your server has that switched on and the book is matched. A book is matched by its ASIN or ISBN, or by an admin in the console. Otherwise it's the one your server holds. If neither has one, your server's [Bridge](../admin/generating-characters-and-recaps.md#a-description-for-the-book) may have written a spoiler-free one, marked **Generated on this server**. diff --git a/docs-users/listening/recaps-and-characters.md b/docs-users/listening/recaps-and-characters.md index e2d1599f..a35110f9 100644 --- a/docs-users/listening/recaps-and-characters.md +++ b/docs-users/listening/recaps-and-characters.md @@ -49,6 +49,7 @@ Material from the community database always comes first. The Bridge also writes ## Good to know -- A book only gets material from the community database when it can be matched (it has an ASIN or ISBN) and someone has written it. Many books have a description and a Series tab, but no characters or recaps yet. +- A book only gets material from the community database when it is matched and someone has written it. Many books have a description and a Series tab, but no characters or recaps yet. +- A book matched only by an admin in the console, with no ASIN or ISBN, shows this material on its page. The companion, Home and downloaded copies need an ASIN or ISBN, so they leave it out. - An admin switches community metadata on or off under **Server > Settings > Community metadata** in the admin console. See [Server settings](../admin/server.md#community-metadata). - A [downloaded](offline-downloads.md#community-notes-offline) book keeps this material, so it works offline. From dc2c6d683115ee5babca78bc13996db4a79e5cd0 Mon Sep 17 00:00:00 2001 From: KodeStar Date: Fri, 13 Nov 2026 12:04:03 +0000 Subject: [PATCH 3/7] Keep the /meta gate list and LibriVox URL rule in one place each The surfaces that ask /meta for a book with no ASIN or ISBN were listed in four pages, and the two User Guide copies already disagreed (one left out the end credits). The list now lives in state-and-data.md and in the User Guide's recaps page, with links from the contract, the data model and the console's books page. The LibriVox URL rule is stated once, in the API reference, which now links the meta links field it reads. Part of LibriVox L5. --- .../architecture/cross-repo-contract.md | 20 ++++++------- docs-developers/frontend/book-page.md | 7 ++--- docs-developers/frontend/state-and-data.md | 9 +++--- docs-developers/server/api/reference.md | 30 +++++++++---------- docs-developers/server/data-model.md | 4 +-- docs-users/admin/books.md | 4 +-- docs-users/listening/recaps-and-characters.md | 2 +- 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/docs-developers/architecture/cross-repo-contract.md b/docs-developers/architecture/cross-repo-contract.md index 2dcda751..b586a117 100644 --- a/docs-developers/architecture/cross-repo-contract.md +++ b/docs-developers/architecture/cross-repo-contract.md @@ -761,8 +761,9 @@ player. - `internal/meta` resolves a book's ASIN or ISBN against `metaserve` and composes an enrichment envelope. A book with neither, or one `metaserve` does not know, is - resolved by its recorded match instead (see [Matches by work id](#matches-by-work-id)). `GET /libraries/{id}/meta` returns it, behind an admin - off-switch and a bounded cache. + resolved by its recorded match instead (see + [Matches by work id](#matches-by-work-id)). `GET /libraries/{id}/meta` returns + it, behind an admin off-switch and a bounded cache. - `GET /meta/work?id=…` serves one work document straight through, from the same cache, for the earlier books of a series the caller does not own. It is authed but **not** library-scoped, since a work id names nothing on this server. @@ -873,11 +874,9 @@ when they find nothing. The envelope is the same, so the wire does not change. The player's book page asks `/meta` for every loaded book that is not an episode (`bookPageMetaEnabled`), so a book matched with neither identifier shows its -community content there. Search's characters ask the listener's started books -with or without identifiers (`characterBooksToLoad`). Every other surface keeps -the identifier gate (`metaEnabledFor`: an ASIN, an ISBN or generated content): -the companion and reveal toast, Home's Now card and Previously on, the end -credits, and the offline copy (`canMatch`). +community content there. Most other surfaces keep the identifier gate +(`metaEnabledFor`). [Enriched book metadata](../frontend/state-and-data.md#enriched-book-metadata) +lists which surface uses which gate. ### Community chapters from the meta side @@ -945,9 +944,10 @@ These are all additive: writes the legal text.** A client must render it beside that content and never compose it. - `recording.chapter_count`. -- `recording.librivox` `{id, url?, zip_url?}`, only on a LibriVox recording. The - server keeps each URL only when it is an absolute http(s) URL and never builds - one from `id`. The player checks `url` again before About links it. +- `recording.librivox` `{id, url?, zip_url?}`, only on a LibriVox recording (see + [`GET /libraries/{id}/meta`](../server/api/reference.md#get-apiv1librariesidmeta)). + The server checks each URL, and the player checks `url` again before About + links it. - `local` `{library_id, path}` on each rail entry the caller owns, in the main view and the orderings. The server resolves `local` **per request, after the shared cache, on a copy**, and never stores it there. A failure placing the caller's books diff --git a/docs-developers/frontend/book-page.md b/docs-developers/frontend/book-page.md index 1c1ab13f..dc771821 100644 --- a/docs-developers/frontend/book-page.md +++ b/docs-developers/frontend/book-page.md @@ -410,10 +410,9 @@ hero and the tabs. `librivox.url` and keeps it only when it is an absolute http(s) URL with a host (`urlEndpoint`). It lower-cases the scheme, because the in-app browser on iOS (`expo-web-browser`) refuses `HTTPS://` and `openExternalUrl` swallows the - error. The server checks the URL too; this is the second check, kept because - the link leaves the app. The envelope carries one recording, the book's own - match, so there is no list to pick from. With no `librivox.url`, or on an - older server, there is no link. + error. The server checks the URL too. The player checks again because the + link leaves the app. With no `librivox.url`, or on an older server, there is + no link. - **Other versions** - `BookVersions`: every other copy (`useBookCopies`) with where it lives and its quality, each opening that copy's page. Nothing shows when there is none. diff --git a/docs-developers/frontend/state-and-data.md b/docs-developers/frontend/state-and-data.md index e21fe6a4..4a751aa0 100644 --- a/docs-developers/frontend/state-and-data.md +++ b/docs-developers/frontend/state-and-data.md @@ -999,10 +999,11 @@ Two gates in `meta-gating.ts` decide whether a screen asks `/meta`: for a book in passing or for every download. The series page's anchor (`metadataAnchor`, an ASIN or ISBN) and Search's -characters (`characterBooksToLoad`, the started books, ids or not) pick their -books by their own rules. An answer that is only generated content goes stale after -2 minutes (`META_GENERATED_ONLY_STALE_MS`), rather than the hour a community -answer keeps. +characters (`characterBooksToLoad`, the started books, identifiers or not) pick +their books by their own rules. + +An answer that is only generated content goes stale after 2 minutes +(`META_GENERATED_ONLY_STALE_MS`), rather than the hour a community answer keeps. `work.generated` drives the "Generated on this server" chip (`GeneratedChip`). `GeneratedMark` heads a Characters panel and the Previously On card's recap, and diff --git a/docs-developers/server/api/reference.md b/docs-developers/server/api/reference.md index 43fc55b5..0facd153 100644 --- a/docs-developers/server/api/reference.md +++ b/docs-developers/server/api/reference.md @@ -2432,15 +2432,15 @@ The server finds the book's work in two steps (`bookEnvelope`): is its `book_matches` row, shown as [`community_match`](#get-apiv1adminlibrariesidbook) on the console's book page: the `work_id` and `recording_id` an admin accepted or a match run applied. The - server fetches that work (`works/{id}`, `meta.Service.EnrichMatch`) and makes - the recording the match names the envelope's recording, as it would a recording - a lookup named. A book with no match row is unmatched. + server fetches that work (`works/{id}`, `meta.Service.EnrichMatch`) and uses + the match's recording as the envelope's recording, as a lookup would. A book + with no match row is unmatched. Either way the server folds the matched recording plus up to three series rails into one envelope, one rail per reading-order family (see [Reading-order families](#reading-order-families)). Every series-rail entry carries its own `web_url`, so a client links -to the metadata site without ever building a URL itself. Answers are cached, by identifier or by the recorded match, in -memory and in the server's database (see +to the metadata site without ever building a URL itself. Answers are cached in +memory and in the server's database, keyed by identifier or by the recorded match (see [Configuration](../configuration.md#community-metadata-metadata)), so a restart serves them warm and a known book keeps its enrichment through a metadata-service outage. @@ -2532,8 +2532,7 @@ Response `200` on a match: `work` is always present on a match. `recording` and `series` are omitted when the upstream has none. Every `omitempty` string or number field (`subtitle`, `first_published`, `description`, `abridged`, `runtime_min`, `release_date`, -`publisher`, `cover_url`, `chapter_count`) is dropped when empty, and so is -`recording.librivox`. +`publisher`, `cover_url`, `chapter_count`) is dropped when empty. - `recording.chapter_count` is the matched recording's chapter count, omitted when the metadata service does not know it. @@ -2552,11 +2551,12 @@ the upstream has none. Every `omitempty` string or number field (`subtitle`, `id` is the LibriVox project id. `url` is the recording's page on librivox.org and `zip_url` the whole recording as one zip, from the community's - `links.librivox` and `links.zip`. Each is optional, and the server keeps it only + `links.librivox` and `links.zip` (see + [The `links` field](../../meta/data-model.md#the-links-field)). Each is + optional, and the server keeps it only when it is an absolute `http` or `https` URL with a host (`librivoxOf`). It never builds either from `id`, because LibriVox's pages are named by slug. A - cached envelope gains the field once its entry is next refreshed. The player - links `url` from About and does not use `zip_url`. + cached envelope gains the field once its entry is next refreshed. - `series[].position` is this work's position in that series. Positions are strings ("1", "2.5", "1-3.5"). - `series[].works` is the full ordered rail, **including the current work**. The @@ -2904,10 +2904,10 @@ content wins **per dimension**: - The whole object is omitted when nothing was generated, or the community has it all. - **A book with no match** (neither its identifiers nor its recorded match find - a work) but generated - content answers `matched: false` **with a work** of its own: `id` empty, - `title` the book's, `authors` `[]`, the generated `characters`, `recaps`, - `recap_summary` and `generated_description`, and `generated`: + a work) but generated content answers `matched: false` **with a work** of its + own: `id` empty, `title` the book's, `authors` `[]`, the generated + `characters`, `recaps`, `recap_summary` and `generated_description`, and + `generated`: ```json { "matched": false, "work": { "id": "", "title": "The Book", "authors": [], "language": "", @@ -3031,7 +3031,7 @@ reach the client. | `400` | `url is required`; `size must be one of [96 160 320 640]` (an empty `size=` too); `path is required` / `invalid library id` | | `401` | missing/invalid token | | `403` | path outside the caller's share scope | -| `404` | `no such cover` - the book is unmatched (no `asin`/`isbn`, or no match upstream) or its envelope hands out no such URL; `no cover` - the image isn't one the server can decode, or is over 40 megapixels; `no book at that path`; `metadata lookup not enabled` (`meta_covers` false) | +| `404` | `no such cover` - the book is unmatched (neither its identifiers nor its recorded match find a work) or its envelope hands out no such URL; `no cover` - the image isn't one the server can decode, or is over 40 megapixels; `no book at that path`; `metadata lookup not enabled` (`meta_covers` false) | | `502` | `metadata service unavailable` - the book's envelope couldn't be had; `code: "cover_unavailable"` - the image couldn't be fetched (an address that isn't public, a timeout, too many redirects, an answer other than `200`), or its fetch failed less than a minute ago | ### `GET /api/v1/meta/work` diff --git a/docs-developers/server/data-model.md b/docs-developers/server/data-model.md index 60ffe97c..946b90ad 100644 --- a/docs-developers/server/data-model.md +++ b/docs-developers/server/data-model.md @@ -1841,8 +1841,8 @@ unknown-ASIN report, upcoming releases, a repick and the Bridge's estimate. `/meta` reads the row only when the book's identifiers find nothing: a book with neither, or one the upstream does not know, answers from its recorded match (see [`GET /libraries/{id}/meta`](api/reference.md#get-apiv1librariesidmeta)). -The player's book page asks `/meta` for any book, but its other surfaces and the -offline copy ask only for a book with an ASIN or ISBN, or with generated content. +Which player surfaces ask `/meta` for such a book is in +[Enriched book metadata](../frontend/state-and-data.md#enriched-book-metadata). The row moves with the book (`moveBookState`, as one set with its edits), is copied from the first part on a join, is kept through a rescan or rebuild, and diff --git a/docs-users/admin/books.md b/docs-users/admin/books.md index 4faea0fb..acedff97 100644 --- a/docs-users/admin/books.md +++ b/docs-users/admin/books.md @@ -89,8 +89,8 @@ listing". A few things need an ASIN or ISBN, so they leave such a book out: the [community chapter check](#community-chapters) and the missing books on the [Series](#series) page. In the players, such a book shows its community details -on its book page only. The companion, Home's cards, the end credits and -downloaded copies leave them out. +on its book page only (see +[Recaps and characters](../listening/recaps-and-characters.md#good-to-know)). ### Unlinking a community match diff --git a/docs-users/listening/recaps-and-characters.md b/docs-users/listening/recaps-and-characters.md index a35110f9..8eba9d1b 100644 --- a/docs-users/listening/recaps-and-characters.md +++ b/docs-users/listening/recaps-and-characters.md @@ -50,6 +50,6 @@ Material from the community database always comes first. The Bridge also writes ## Good to know - A book only gets material from the community database when it is matched and someone has written it. Many books have a description and a Series tab, but no characters or recaps yet. -- A book matched only by an admin in the console, with no ASIN or ISBN, shows this material on its page. The companion, Home and downloaded copies need an ASIN or ISBN, so they leave it out. +- A book matched only by an admin in the console, with no ASIN or ISBN, shows this material on its page. The companion, Home's cards, the end credits and downloaded copies need an ASIN or ISBN, so they leave it out. - An admin switches community metadata on or off under **Server > Settings > Community metadata** in the admin console. See [Server settings](../admin/server.md#community-metadata). - A [downloaded](offline-downloads.md#community-notes-offline) book keeps this material, so it works offline. From 3ef40aa6f7e56fbfe644ecb5a4eb1cc8585aa4d2 Mon Sep 17 00:00:00 2001 From: KodeStar Date: Fri, 13 Nov 2026 12:04:03 +0000 Subject: [PATCH 4/7] Update the gate wording /meta's recorded-match lookup left stale The attachments page said the player asks /meta for a book with no ASIN or ISBN only through generated content, and the podcasts page named only metaEnabledFor as the episode guard; the book page now asks for any book through bookPageMetaEnabled. Also plainer sentences and reflowed lines in the offline, configuration and console pages. Part of LibriVox L5. --- docs-developers/bridge/attachments.md | 3 ++- docs-developers/frontend/offline.md | 2 +- docs-developers/frontend/podcasts.md | 5 +++-- docs-developers/server/configuration.md | 7 +++---- docs-developers/server/web-ui.md | 3 +-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs-developers/bridge/attachments.md b/docs-developers/bridge/attachments.md index dbd09ee2..1b6a7381 100644 --- a/docs-developers/bridge/attachments.md +++ b/docs-developers/bridge/attachments.md @@ -393,7 +393,8 @@ book's signature names a characters, recaps or description file. chapters, and a map is how its text lines up; neither is generated content. - It is read from the narrow column, so a list never joins `book_attachments`. - Every book object carries it: lists, search, `item`, recent, and the next book's - `book`. The player uses it to ask `/meta` for a book with no ASIN or ISBN. + `book`. The player's identifier-gated surfaces (`metaEnabledFor`) use it to ask + `/meta` for a book with no ASIN or ISBN. Off the wire, `Book.GeneratedParts` says which parts the signature names. So `/meta` and the console's book page read only the columns a book has. diff --git a/docs-developers/frontend/offline.md b/docs-developers/frontend/offline.md index 67bf9746..c4fa1816 100644 --- a/docs-developers/frontend/offline.md +++ b/docs-developers/frontend/offline.md @@ -992,7 +992,7 @@ a marker (`DownloadManifest.meta = { savedAt }`). - *What `captureOfflineMeta` skips.* A book with no ASIN or ISBN and no generated content (`canMatch`, `metaEnabledFor` on the cached full item first, since the manifest's book can be the list shape). That includes a book matched only in the - console: online its page shows the match, offline it reads as unmatched. Also a + console. Online its page shows the match. Offline it reads as unmatched. Also a server without `metadata`. - *How it reads.* It reads through the screens' own option factories (`bookMetaQuery`, `metaWorkQuery`), so it reuses a fresh answer they hold. It never diff --git a/docs-developers/frontend/podcasts.md b/docs-developers/frontend/podcasts.md index a1fd6f9c..857240de 100644 --- a/docs-developers/frontend/podcasts.md +++ b/docs-developers/frontend/podcasts.md @@ -193,8 +193,9 @@ audio changed and the place is kept. Nothing is rescaled. There is no Generate, community metadata, rating or series. The player's companion chips and tabs are off for an episode (`useCompanionData` reads `book.episode` as `off`). The player never asks for community metadata for an -episode: `metaEnabledFor` in `meta-gating.ts` is false for `book.episode`, because -a feed's tags can carry an ISBN-like value. +episode: `bookPageMetaEnabled` in `meta-gating.ts`, which `metaEnabledFor` builds +on, is false for `book.episode`, because a feed's tags can carry an ISBN-like +value. ### Wording in the player and on Home diff --git a/docs-developers/server/configuration.md b/docs-developers/server/configuration.md index 235cbec4..a8358308 100644 --- a/docs-developers/server/configuration.md +++ b/docs-developers/server/configuration.md @@ -692,10 +692,9 @@ makes the cache survive a restart and an outage: It never holds a service error, and never a row for a work id that `/meta/work` or `previous` found unknown. That id is the caller's choice, so storing misses - would let any user grow the table. A - `404` from the service for a work already stored replaces its row, so a dropped - work isn't served again in a later outage. In mirror mode a `404` the local copy - gave never does (see [Mirror mode](#mirror-mode-metadatamode-mirror)). + would let any user grow the table. A `404` from the service for a work already + stored replaces its row, so a dropped work isn't served again in a later + outage. In mirror mode a `404` the local copy gave never does (see [Mirror mode](#mirror-mode-metadatamode-mirror)). - **How it is read:** after a memory miss. A row still within its TTL is served and warms memory for the rest of that TTL. A match past its TTL is not served while the service answers, but when the service fails it is served anyway diff --git a/docs-developers/server/web-ui.md b/docs-developers/server/web-ui.md index a256c095..2a25d481 100644 --- a/docs-developers/server/web-ui.md +++ b/docs-developers/server/web-ui.md @@ -557,8 +557,7 @@ metadata is off: - The gaps come from the `GET /libraries/{id}/meta` of each series' first book with an ASIN or ISBN (`metaCandidate`, `hasIdentifier`). A book matched with - neither is never the one asked, although `/meta` would answer it from its - recorded match. + neither is skipped, although `/meta` would answer it from its recorded match. - The card offers "Match a book" when that `/meta` found no match. When no book has an ASIN or ISBN, it offers it when none of the series is matched. - Each card resolves its books with an ASIN or ISBN to community work ids From c477881b0d6530746ca731304034888a863fc59e Mon Sep 17 00:00:00 2001 From: KodeStar Date: Fri, 13 Nov 2026 12:08:24 +0000 Subject: [PATCH 5/7] Say which lookups read a book's recorded match and which do not /next's community step still finds a book by ASIN or ISBN alone, but its wording now read as if a console-matched book qualified. The matched field's note claimed /meta answers every matched book, which fails for an unknown ASIN with no match row. A match naming no recording, or one the work dropped, gets the work's first recording, which can itself be a LibriVox one. The 500 'could not load book' also comes from loading the book, and /meta/cover returns it too. Part of LibriVox L5. --- docs-developers/server/api/reference.md | 17 ++++++++++------- docs-developers/server/data-model.md | 5 +++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs-developers/server/api/reference.md b/docs-developers/server/api/reference.md index 0facd153..fa7e16bc 100644 --- a/docs-developers/server/api/reference.md +++ b/docs-developers/server/api/reference.md @@ -2322,8 +2322,9 @@ its other series (`series_list`) in their order. The community rails answer firs but only when they can **place** the next entry on one of the caller's books. Otherwise the local steps decide, in order, and the first one with an answer wins: -1. **`community`**: community metadata is on and the book matches a work with at - least one series rail. +1. **`community`**: community metadata is on and the book's ASIN or ISBN matches + a work with at least one series rail. Unlike `/meta`, this step never reads + the book's recorded match. - **Rail order.** The rails are taken in the book's own order. First comes a rail named like its main series (by the rail's name or one of its orderings', ignoring case, accents, punctuation and spacing). Then come rails named like @@ -2433,8 +2434,9 @@ The server finds the book's work in two steps (`bookEnvelope`): [`community_match`](#get-apiv1adminlibrariesidbook) on the console's book page: the `work_id` and `recording_id` an admin accepted or a match run applied. The server fetches that work (`works/{id}`, `meta.Service.EnrichMatch`) and uses - the match's recording as the envelope's recording, as a lookup would. A book - with no match row is unmatched. + the match's recording as the envelope's recording, as a lookup would. When + the match names no recording, or one the work no longer has, that is the + work's first. A book with no match row is unmatched. Either way the server folds the matched recording plus up to three series rails into one envelope, one rail per reading-order family (see @@ -2972,7 +2974,7 @@ generated description, because it is a field they don't know. | `400` | missing `path` / invalid library id | | `403` | path outside the caller's share scope | | `404` | `no book at that path`, **or** metadata lookup is disabled on this server (`metadata` capability false) | -| `500` | `could not load book` - the book's recorded match could not be read. With `spoilers=hide`, also `could not load progress` - the caller's progress could not be read; the ungated envelope is never sent instead | +| `500` | `could not load book` - the book or its recorded match could not be read. With `spoilers=hide`, also `could not load progress` - the caller's progress could not be read; the ungated envelope is never sent instead | | `502` | `metadata service unavailable` - the upstream was unreachable or errored, the server holds no earlier answer for the book, and it has no generated content | ### `GET /api/v1/libraries/{id}/meta/cover` @@ -3032,6 +3034,7 @@ reach the client. | `401` | missing/invalid token | | `403` | path outside the caller's share scope | | `404` | `no such cover` - the book is unmatched (neither its identifiers nor its recorded match find a work) or its envelope hands out no such URL; `no cover` - the image isn't one the server can decode, or is over 40 megapixels; `no book at that path`; `metadata lookup not enabled` (`meta_covers` false) | +| `500` | `could not load book` - the book or its recorded match could not be read | | `502` | `metadata service unavailable` - the book's envelope couldn't be had; `code: "cover_unavailable"` - the image couldn't be fetched (an address that isn't public, a timeout, too many redirects, an answer other than `200`), or its fetch failed less than a minute ago | ### `GET /api/v1/meta/work` @@ -6457,8 +6460,8 @@ is a `400` too (`too many format or codec values`). - `matched` - the book has an ASIN or ISBN, or a recorded community match (a store listing with neither, from 2.6.0): the same rule as the `matched` filter (`catalog.matchedExpr`). The community chapter check finds a book by its ASIN or - ISBN alone, so check `asin` / `isbn` for it, not `matched`. `/meta` answers a - `matched` book either way. + ISBN alone, so check `asin` / `isbn` for it, not `matched`. `/meta` also reads + the recorded match, so it can answer a `matched` book with neither identifier. - `abridged` - `true`, `false`, or `null` while nothing says (always present, unlike the player's `Book`, which omits it). `genres` and `tags` are `[]` for none. diff --git a/docs-developers/server/data-model.md b/docs-developers/server/data-model.md index 946b90ad..a29b1c9b 100644 --- a/docs-developers/server/data-model.md +++ b/docs-developers/server/data-model.md @@ -1836,9 +1836,10 @@ this row alone. `catalog.matchedExpr` - a refresh run's `MatchedBooks`. What needs an identifier tests `asin` or `isbn` itself: community chapters, the -unknown-ASIN report, upcoming releases, a repick and the Bridge's estimate. +unknown-ASIN report, upcoming releases, a repick, the Bridge's estimate and the +community step of `/next`. -`/meta` reads the row only when the book's identifiers find nothing: a book with +`/meta` reads the row only when the book's identifiers find nothing. A book with neither, or one the upstream does not know, answers from its recorded match (see [`GET /libraries/{id}/meta`](api/reference.md#get-apiv1librariesidmeta)). Which player surfaces ask `/meta` for such a book is in From 60da16d7aa1dcb6d317327ddfc9068edafbb702f Mon Sep 17 00:00:00 2001 From: KodeStar Date: Fri, 13 Nov 2026 12:08:25 +0000 Subject: [PATCH 6/7] Tell listeners where a console-matched book shows community notes Search's characters ask for a started book with or without an ASIN or ISBN, so such a book's characters show there too. A download of it keeps no community notes offline, which the offline page said every download does. Part of LibriVox L5. --- docs-users/admin/books.md | 2 +- docs-users/listening/offline-downloads.md | 2 +- docs-users/listening/recaps-and-characters.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs-users/admin/books.md b/docs-users/admin/books.md index acedff97..5c4888e6 100644 --- a/docs-users/admin/books.md +++ b/docs-users/admin/books.md @@ -89,7 +89,7 @@ listing". A few things need an ASIN or ISBN, so they leave such a book out: the [community chapter check](#community-chapters) and the missing books on the [Series](#series) page. In the players, such a book shows its community details -on its book page only (see +on its book page and in Search's characters, but not offline (see [Recaps and characters](../listening/recaps-and-characters.md#good-to-know)). ### Unlinking a community match diff --git a/docs-users/listening/offline-downloads.md b/docs-users/listening/offline-downloads.md index ae329671..4060c15c 100644 --- a/docs-users/listening/offline-downloads.md +++ b/docs-users/listening/offline-downloads.md @@ -92,7 +92,7 @@ When your device has no network, downloaded books play and everything else waits ## Community notes offline -When your server has [community metadata](book-page.md#about-this-book) switched on, a downloaded book keeps its description, [recaps and characters](recaps-and-characters.md) and Series tab. They still [hide what you haven't reached](recaps-and-characters.md#nothing-gets-spoiled-before-you-reach-it). They're refreshed when the copy is over a week old and the app can reach your server. +When your server has [community metadata](book-page.md#about-this-book) switched on, a downloaded book keeps its description, [recaps and characters](recaps-and-characters.md) and Series tab. They still [hide what you haven't reached](recaps-and-characters.md#nothing-gets-spoiled-before-you-reach-it). They're refreshed when the copy is over a week old and the app can reach your server. A book with no ASIN or ISBN, matched only by an admin in the console, doesn't keep them. ## Downloads in a web browser diff --git a/docs-users/listening/recaps-and-characters.md b/docs-users/listening/recaps-and-characters.md index 8eba9d1b..2bdb3506 100644 --- a/docs-users/listening/recaps-and-characters.md +++ b/docs-users/listening/recaps-and-characters.md @@ -50,6 +50,6 @@ Material from the community database always comes first. The Bridge also writes ## Good to know - A book only gets material from the community database when it is matched and someone has written it. Many books have a description and a Series tab, but no characters or recaps yet. -- A book matched only by an admin in the console, with no ASIN or ISBN, shows this material on its page. The companion, Home's cards, the end credits and downloaded copies need an ASIN or ISBN, so they leave it out. +- A book matched only by an admin in the console, with no ASIN or ISBN, shows this material on its page and in Search. The companion, Home's cards, the end credits and downloaded copies need an ASIN or ISBN, so they leave it out. - An admin switches community metadata on or off under **Server > Settings > Community metadata** in the admin console. See [Server settings](../admin/server.md#community-metadata). - A [downloaded](offline-downloads.md#community-notes-offline) book keeps this material, so it works offline. From 2100232bebc67161788659b7a96f2776bafa6f35 Mon Sep 17 00:00:00 2001 From: KodeStar Date: Fri, 13 Nov 2026 12:08:25 +0000 Subject: [PATCH 7/7] Split colon-joined sentences in the /meta and LibriVox notes The Wording rules allow no colon as a mid-sentence connector. Part of LibriVox L5. --- docs-developers/architecture/cross-repo-contract.md | 7 ++++--- docs-developers/frontend/book-page.md | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs-developers/architecture/cross-repo-contract.md b/docs-developers/architecture/cross-repo-contract.md index b586a117..23b937e0 100644 --- a/docs-developers/architecture/cross-repo-contract.md +++ b/docs-developers/architecture/cross-repo-contract.md @@ -868,9 +868,10 @@ looks such a book up by `works/{id}`, following a retired slug's `301`. The rule run applies to such a book is in [Bulk community matching](../server/api/reference.md#bulk-community-matching). -From 2.7.0 the server's `/meta` answers such a book from its recorded match: the -book's identifiers first, then its `book_matches` `work_id` and `recording_id` -when they find nothing. The envelope is the same, so the wire does not change. +From 2.7.0 the server's `/meta` answers such a book from its recorded match. It +tries the book's identifiers first, then its `book_matches` `work_id` and +`recording_id` when they find nothing. The envelope is the same, so the wire does +not change. The player's book page asks `/meta` for every loaded book that is not an episode (`bookPageMetaEnabled`), so a book matched with neither identifier shows its diff --git a/docs-developers/frontend/book-page.md b/docs-developers/frontend/book-page.md index dc771821..3fc7e8b7 100644 --- a/docs-developers/frontend/book-page.md +++ b/docs-developers/frontend/book-page.md @@ -404,8 +404,9 @@ hero and the tabs. - A matched book without community text keeps "View on AudioSilo Meta". Long text folds past six lines with Show more. - A LibriVox recording adds "Also free on LibriVox" (`book.about.librivox`) under - the other links out: after the credit and **Improve this**, else after "View on - AudioSilo Meta". It opens `librivoxUrl(recording)` through `openExternalUrl`. + the other links out. It comes after the credit and **Improve this**, else + after "View on AudioSilo Meta". It opens `librivoxUrl(recording)` through + `openExternalUrl`. `librivoxUrl` (`book-details-model.ts`) reads the matched recording's `librivox.url` and keeps it only when it is an absolute http(s) URL with a host (`urlEndpoint`). It lower-cases the scheme, because the in-app browser on iOS