Split series total_book_count into total_volume_count and total_chapter_count#8
Merged
Conversation
Deploying codex with
|
| Latest commit: |
2b8f979
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://2b111de7.codex-asm.pages.dev |
| Branch Preview URL: | https://metadata-count-split.codex-asm.pages.dev |
…er counts Replace the overloaded series_metadata.total_book_count with two semantically distinct fields, total_volume_count (Option<i32>) and total_chapter_count (Option<f32>), each with its own lock. The single column was ambiguous: it meant volumes for volume-organized libraries and chapters for chapter-organized ones, producing incoherent counts like "109/14" when providers returned volume totals against a local chapter-organized series. This change adds the new schema and supporting plumbing without yet touching read sites, plugin protocol, or DTOs: - Migration m20260502_000067_split_book_count adds the four new columns and backfills total_volume_count + lock from the legacy total_book_count + lock. The legacy column is kept for now and will be dropped in a follow-up once all read sites are migrated. - SeriesMetadata entity gains the four new fields; legacy fields are marked deprecated. All explicit ActiveModel constructors are updated. - SeriesMetadataRepository gains update_total_volume_count and update_total_chapter_count, and set_lock / is_field_locked recognise the new lock keys. - Adds backfill and round-trip tests, including fractional f32 chapter counts and independent lock toggling.
…in protocol Extend PluginSeriesMetadata and UserLibraryEntry with total_volume_count (Option<i32>) and total_chapter_count (Option<f32>) so providers can return the two semantically distinct counts separately. Legacy total_book_count remains on the wire and is doc-flagged DEPRECATED for one phase of backward-compat with older plugins; it will be removed once the apply pipeline and DTOs migrate. Add MetadataWriteTotalVolumeCount and MetadataWriteTotalChapterCount permissions (metadata:write:total_volume_count and metadata:write:total_chapter_count), wired into as_str/from_str, the all_write_permissions and common_write_permissions lists, and the metadata:write:* wildcard branch in has_permission. Bump PROTOCOL_VERSION from 1.0 to 1.1 (additive minor): 1.0 plugins continue to deserialize cleanly because total_book_count is still in the schema. Tests cover serde round-trip for the new fields, legacy total_book_count deserialization, skip-when-unset behavior, permission round-trip, and wildcard permission grants.
…MetadataApplier Wire writes for the two new count fields through the existing MetadataApplier pipeline so plugins can populate them end-to-end. Each new field reuses the established allowlist + lock + permission + repo-update + applied/skipped tracking pattern, with no logic divergence: - totalVolumeCount: gated by MetadataWriteTotalVolumeCount and total_volume_count_lock; writes via update_total_volume_count. - totalChapterCount: gated by MetadataWriteTotalChapterCount and total_chapter_count_lock; writes via update_total_chapter_count. The legacy totalBookCount block is left in place and now carries a DEPRECATED doc-comment marking it for backward-compat-only writes; it will be replaced by a fallback that routes legacy plugin payloads to totalVolumeCount, and removed when total_book_count is dropped from the schema. Adds integration tests covering happy-path writes, fractional chapter round-trips, both-fields-at-once, lock skipping with reason, permission denial, allowlist filtering, and absent-value no-op behavior.
…writing total_book_count MetadataApplier no longer writes the legacy total_book_count column. A backward-compat fallback routes legacy `total_book_count` payloads from older plugins into `total_volume_count`, with a one-time-per-plugin deprecation warning. The legacy column is now frozen. Switch every internal read of total_book_count to total_volume_count (and surface total_chapter_count where applicable): - services: completion filter, export collector (new ExpectedChapterCount field), recommendations protocol, plugin library entries, preprocessing context (new totalVolumeCount / totalChapterCount template field-access paths), user-plugin-sync push (now also surfaces total_chapters on SyncProgress). - v1 API handlers: series, recommendations, plugin_actions (separate totalVolumeCount and totalChapterCount previews), bulk_metadata. - Komga compatibility: handler maps the volume-shaped totalBookCount and the oneshot heuristic from total_volume_count. DTOs gain total_volume_count and total_chapter_count (and their lock counterparts) alongside the legacy fields. Both are populated for one phase to give clients and plugins a window to migrate; the legacy fields and the database column will be dropped in a later phase. PUT, PATCH, and lock-update handlers prefer the new fields and fall back to the legacy field when only it is provided. Tests cover the legacy fallback, new-field precedence, and the frozen-column invariant; pre-existing fixtures that seeded total_book_count are updated to use total_volume_count.
…om MangaBaka and AniList Extend the TypeScript SDK's PluginSeriesMetadata, UserLibraryEntry, and Recommendation types with totalVolumeCount (number) and totalChapterCount (number, fractional supported), mark totalBookCount @deprecated, and widen the manifest protocolVersion union to "1.0" | "1.1" so updated plugins can declare 1.1 without breaking 1.0 consumers. Update the MangaBaka metadata plugin to map both fields from MangaBaka's final_volume and total_chapters response fields via dedicated parser helpers that reject null/empty/non-numeric/non-positive inputs. The legacy totalBookCount continues to mirror the volume count for one phase of backward-compat with older Codex versions. Bump the manifest to protocol 1.1 and add fixture tests for the basic case, fractional chapter counts, null handling, and garbage input. Update the AniList recommendations plugin to fetch Media.chapters from the GraphQL recommendations query and derive totalVolumeCount and totalChapterCount in convertRecommendations, filtering zero/negative values and mirroring the volume count to legacy totalBookCount. Bump the manifest to protocol 1.1. The existing volumes coverage was rewritten to assert the new fields plus mirroring, with new cases for chapters-only, both-fields-known, and zero/negative rejection. MAL is skipped (no metadata-mal plugin in this repo). OpenLibrary is left as-is since its mapper does not currently populate totalBookCount, so there is nothing to migrate yet.
Add an optional `format` field to the plugin `SearchResultPreview` protocol so search-result rows can carry a content-type discriminator (manga, novel, light_novel, manhwa, manhua, comic, webtoon, one_shot, etc.). The field is free-form at the protocol layer so plugin authors are not locked into an enum that requires Codex core changes when new formats appear; the recommended vocabulary is documented on the field. The MangaBaka plugin populates it from `series.type` (already lowercase snake_case, no conversion needed). The metadata search modal renders a colored badge ahead of the existing status/bookCount/genres badges: grape for the manga family, teal for novels, orange for comics, gray fallback for unknowns with the raw value title-cased. A small helper module owns the color/label resolution so the mapping is unit-testable. This fixes a recurring papercut where MangaBaka returns two visually identical rows for the same title (e.g. one tagged manga, one tagged novel) and the only way to tell them apart was to click through to the provider. The data was already on the wire from MangaBaka; it just was not propagated through the plugin protocol or rendered in the modal. Tests added at every layer (protocol round-trip + old-shape compat, mapper, badge helper, modal rendering with distinct colors). OpenAPI spec and TypeScript types regenerated.
Replaces the single "Total Books" surface across the frontend with separate volume and chapter counts, matching the metadata schema split. - Add seriesCounts helper that formats the series detail header line based on which axes are known: both → "<local>/<vol> vol · <chap> ch", volumes only → "<local>/<vol> vol", chapters only → "<local>/<chap> ch" (the chapter-organized fix), legacy "<local> books" fallback, and nothing rendered when neither side has data. - SeriesDetail header switches to the helper, reading totalVolumeCount and totalChapterCount directly instead of falling back through totalBookCount. - SeriesMetadataEditModal splits the single Total Books field into Total Volumes (int) and Total Chapters (float) with independent lock toggles; save handler parses both and writes them on the PATCH. - BulkMetadataEditModal applies the same split to series form state, lock field list, and UI; bulk patch payload now carries both new fields. - ConditionsEditor field/lock option lists swap the legacy paths for the new totalVolumeCount and totalChapterCount entries. - SeriesMetadata grid component renders separate Total Volumes and Total Chapters tiles. Tests added for the formatter, the manual-edit round-trip (including fractional chapter counts), and the bulk patch round-trip.
Strip the deprecated `totalBookCount` (and its lock counterpart) from every v1 request and response shape: SeriesMetadata, PatchSeriesMetadataRequest, SeriesMetadataResponse, MetadataLocks, FullSeriesMetadataResponse, SeriesFullMetadata, MetadataContextDto, UpdateMetadataLocksRequest, BulkPatchSeriesMetadataRequest, and RecommendationDto. Update v1 handlers across series, bulk_metadata, and recommendations to drop the matching read/write paths. The plugin-protocol fallback that maps a legacy `total_book_count` from older plugins onto `total_volume_count` is kept, along with the entity-side legacy-column setters, until the column itself is dropped. Add `metadata:write:total_volume_count` and `metadata:write:total_chapter_count` to the documented permission allowlist; the legacy `metadata:write:total_book_count` permission remains until the column drop. OPDS 1.2 and OPDS 2.0 expose no expected-total counts and need no changes. The Komga compatibility DTO retains `totalBookCount` on the wire because Komga clients depend on that name; that mapping is owned by the next phase of the migration. Regenerate the OpenAPI spec and TypeScript types so the frontend consumes only the new fields. Tests updated where they referenced the removed DTO field.
…ve totalBookCount wire format Rename the internal field on `KomgaSeriesMetadataDto` from `total_book_count` to `total_volume_count` to match the canonical source field on `series_metadata`. Keep Komga's wire format unchanged with explicit `#[serde(rename = "totalBookCount" / "totalBookCountLock")]` so Komga clients (Komic, Mihon, etc.) continue to see the field names they expect. The struct-level `rename_all = "camelCase"` would otherwise emit `totalVolumeCount`. Komga upstream has no chapter-count field, so `total_chapter_count` is intentionally not surfaced on the Komga DTO; chapter data is exposed only via the native v1 API. Update the handler in routes/komga/handlers/series.rs to populate the renamed fields from `series_metadata.total_volume_count` (and lock). Add tests covering the wire shape end-to-end: a Komic-style PUT-payload roundtrip on the DTO, an Option-skip assertion when the count is None, and an integration test against the live handler that asserts `metadata.totalBookCount` is populated from `total_volume_count`, no internal field name leaks, and no chapter-count field is invented. Regenerate web/openapi.json, docs/api/openapi.json, and web/src/types/api.generated.ts to match.
…l, and DTOs Hard-remove the overloaded total_book_count field everywhere it still exists outside the Komga compatibility layer, completing the migration to total_volume_count + total_chapter_count. Schema: new migration drops series_metadata.total_book_count and total_book_count_lock; down() re-adds them as nullable column + non-null default-false bool with no data restore. Plugin protocol bumped to 1.2 (breaking minor): PluginSeriesMetadata and UserLibraryEntry lose total_book_count on the wire, and the MetadataWriteTotalBookCount permission is removed from the enum, parser, display, and permission-set builders. Older plugins that still emit the legacy field round-trip silently — serde drops the unknown value with no fallback path. MetadataApplier loses its legacy routing block and the per-plugin deprecation-warning helper. The plugin-actions preview shim that or'd total_book_count into the proposed volume count is gone. SeriesMetadataRepository::update_total_book_count() and the "total_book_count" branches in set_lock / is_field_locked are removed. Read-site sweep: Recommendation, MetadataContext (preprocessing), the recommendations handler fallback, the user-library push entries, and the v1 plugin permission allowlist are now split-only. SDK TypeScript types (protocol.ts, recommendations.ts) lose the deprecated field from PluginSeriesMetadata, UserLibraryEntry, and Recommendation. Plugins: metadata-echo, metadata-mangabaka, and recommendations-anilist no longer mirror the volume value into a legacy field; matching test assertions are pruned. Frontend: MetadataPreview labels swap "Total Books" for separate "Total Volumes" + "Total Chapters" entries; MetadataSearchModal reads totalVolumeCount; RecommendationCard renders separate vol/ch badges; templateUtils, exampleTemplates Handlebars samples, and the MSW mock handlers carry the split fields and split locks. The web permission union/lookup adds metadata:write:total_volume_count and metadata:write:total_chapter_count, dropping the legacy permission. The Komga API DTO keeps its serde-renamed totalBookCount wire field intact — Komga clients (Komic, Mihon, etc.) still see the field name they expect. Migration test coverage: a new SQLite test runs the count-split + drop pair step-by-step and asserts the legacy columns disappear while the split-count columns survive. Three integration tests in metadata_apply that exercised the (now-gone) backward-compat fallback are removed along with their helper.
Add the user-facing and plugin-author documentation for the metadata count split (total_volume_count + total_chapter_count replacing the legacy total_book_count). User docs: - New series-metadata.md page covering volumes vs. chapters semantics, the four total-display variants, manual-edit instructions with per-field locks, what each first-party plugin populates, migration notes, and the Komga compatibility caveat. Wired into the docs sidebar between book-metadata and custom-metadata. - Sweep stale totalBookCount references in custom-metadata.md (field table + Handlebars examples), preprocessing-rules.md (field table), and plugins/anilist-sync.md (Completed-status criterion). Plugin author guide: - Add a "Volume and Chapter Counts" subsection to writing-plugins.md with a field-comparison table, mapping examples for MangaBaka, AniList, and volume-only providers, and a protocol-1.2 deprecation note. Update the example provider's get() to populate both fields. - Replace totalBookCount with totalVolumeCount and totalChapterCount on the PluginSeriesMetadata interface in sdk.md and on the metadata/series/get example response in protocol.md. Changelog: - Prepend an [unreleased] block calling out the breaking API DTO change, the plugin-protocol 1.2 bump, and the Handlebars template context change, plus the additive features (per-axis split + the search-result format discriminator).
Adds the local counterpart to the world totals (total_volume_count / total_chapter_count) by splitting per-book classification into sibling volume and chapter columns on book_metadata. The populated/null pairing across the two columns derives the kind of book (volume / chapter / chapter-of-volume / unknown) without an enum, enabling the local_max_volume / local_max_chapter aggregations release-tracking needs. - Migration adds chapter (REAL) + chapter_lock (BOOLEAN) to book_metadata with up/down coverage. - Entity gains chapter + chapter_lock; BookMetadataRepository gains update_chapter, update_volume, and a set_lock(field, locked) helper mirroring the series_metadata pattern. - Renames the BookNamingStrategy trait to BookMetadataStrategy (alias retained) and extends it with resolve_volume / resolve_chapter. Each strategy implements per its semantics: Filename uses a structured regex, MetadataFirst reads ComicInfo only, Smart prefers ComicInfo with a filename fallback, SeriesName passes through context, and Custom delegates to its existing named-group extraction. - New structured filename parser uses a lenient prefix (v / vol / volume, c / ch / chapter), strict left boundary, first-match-wins per axis, no bare-number fallback. Fractional volumes are rejected (column is i32) and fractional chapters are preserved (e.g. 47.5 side chapters). - Scanner wires the classification into both metadata-write paths so the active strategy populates volume + chapter on every analysis, with per-field locks gating writes the same way they gate every other field. Tests added at the migration, repository, strategy, and parser layers.
…icInfo Wire the per-book classification trait into the analyzer's metadata write paths and add a one-time backfill over already-scanned libraries. - ComicInfo gains a structured `chapter: Option<f32>` derived from `<Number>` (handles fractional chapters like 47.5; non-numeric values leave chapter null while preserving the raw string for legacy display). - analyzer_queue threads `comic_info.chapter` into the strategy input and uses `comic_info.chapter` as the existing-row update fallback, matching the volume rule and restoring symmetry between the two axes. - New `m20260503_000070_backfill_book_volume_chapter` migration re-parses each book's filename in 1000-row batches, populating volume and chapter only where currently null. Strictly additive: never overwrites a populated value, never touches lock fields. Idempotent on rerun. The filename parser is duplicated inline since the migration crate cannot depend on the main crate. - BookMetadataApplier gains volume and chapter write blocks following the existing per-field lock + permission pattern. Plugin-protocol f64 volume is narrowed to i32 with fractional rejection rather than silent truncation. New `metadata:write:volume` and `metadata:write:chapter` permissions; both flow through the `metadata:write:*` wildcard. Tests cover the strategy x parse-case matrix, ComicInfo `<Number>` derivation, the backfill (full case matrix + additive-only + idempotency), and the new applier blocks (write, fractional preserved or rejected, lock-honored, permission-missing-skipped, allowlist).
… display to <max>/<total>
Adds per-series aggregates derived from book_metadata.volume / chapter so
the series detail header can render `14/17 vol · 137/158 ch` instead of
the file-count-based `17/17 vol`, which was incoherent for libraries
that mix complete volumes with loose chapters.
- Repo: new BookClassificationAggregates struct and
get_book_classification_aggregates{,_for_series_ids} on
SeriesRepository. Single SQL pass: MAX(volume), MAX(chapter), and
SUM(CASE WHEN volume IS NOT NULL AND chapter IS NULL THEN 1 ELSE 0)
over books LEFT JOIN book_metadata grouped by series_id. Works on
SQLite and PostgreSQL.
- DTOs: SeriesDto and FullSeriesResponse gain optional localMaxVolume,
localMaxChapter, and volumesOwned (skipped when None,
schema-documented). Both the single-series helper and the batched
variant feed the new aggregator (added to the existing tokio::join!
block so list endpoints stay one round-trip).
- Frontend: formatSeriesCounts gains optional localMaxVolume and
localMaxChapter inputs. When present they replace the file-count
numerator on each axis; when absent the legacy formatting is
preserved verbatim. Wired into SeriesDetail.
- OpenAPI regenerated; new fields land in the TypeScript client types.
Tests cover mixed/empty/unclassified series at the repo layer, the
DTO round-trip via /api/v1/series/{id}, and the new formatter
branches alongside every existing case.
…classification in the UI Adds the chapter axis end-to-end on per-book metadata, completing the count-split work down to the individual file. Previously chapter lived only on the entity and series-level aggregates; the per-book DTOs and frontend had no way to read or write it. API: - Add `chapter: Option<f32>` and `chapter_lock: bool` to BookMetadataDto, BookFullMetadata, BookMetadataResponse, ReplaceBookMetadataRequest, PatchBookMetadataRequest (PatchValue<f32>), BookMetadataLocks, UpdateBookMetadataLocksRequest, and BookMetadataContextDto. - Wire the new field through both write paths in replace_book_metadata and patch_book_metadata with the same auto-lock-on-set rule as `volume`. - update_book_metadata_locks accepts and writes `chapter_lock`. - Preprocessing context exposes `chapter` and `chapter_lock` plus a new `"chapter"` accessor in get_metadata_field for templates and conditions. - Regenerate OpenAPI spec and TypeScript types. UI: - New BookKindBadge component classifies a book by which of (volume, chapter) are populated. Four cases: volume-only -> "Vol N", chapter-only -> "Ch N", both -> "Vol V . Ch C" (single combined badge), neither -> muted "Vol" with explanatory tooltip. Wired into the book detail header next to BookTypeBadge. - BookMetadataEditModal gains a Chapter LockableInput between Volume and Count in the Publication tab, with step="any" so fractional values like 42.5 round-trip cleanly. Independent lock toggle from volume's. - Strategy UI: rename the user-facing label "Book Naming Strategy" to "Book Metadata Strategy" with the description "How book metadata (title, volume, chapter) is extracted from files". Per-strategy descriptions rewritten to call out volume/chapter behavior. The server-side BookStrategy enum is unchanged to avoid migrating stored library configs. Tests added for the new badge component, the chapter round-trip through the edit modal, and the renamed Strategy UI label and description.
Add a Volume and Chapter Numbers section to book-metadata.md covering the classification matrix, the canonical filename markers (vN, cN, Vol.N, Chapter N, v15 - c126, fractional c042.5), the boundary and fractional-volume rules, and the strategy-conditional ComicInfo override behavior. Cross-link the Filename / Smart / Metadata First / Custom sections in book-strategies.md so each strategy explains what it does for volume and chapter, and refresh the Custom-strategy named-group reference with a fractional-chapter regex variant. Extend examples.md with two new worked custom-regex examples (chapter-only libraries with range filenames; volume-of-series + issue number for Western comics) and add explanatory text to the existing scanlation-bracketed and SxxExx examples. Fix a broken link in series-metadata.md (the dev plugin author guide lives under a separate Docusaurus content plugin and needs an absolute path) so the docs build succeeds. Update CHANGELOG with two unreleased Features entries covering per-book classification across all four book strategies and the series-detail count-display switch to local_max/total.
…Postgres The book volume/chapter backfill migration constructed its per-row UPDATE with hand-rolled SQL containing `?` placeholders and shipped it through `Statement::from_sql_and_values`. SeaORM does not rewrite placeholders to match the target backend, so on PostgreSQL the literal `?` reached the server and parsing failed near `WHERE`, aborting database init for any fresh Postgres deployment. SQLite tolerated `?` and masked the bug locally. Switch the UPDATE to `sea_query::Query::update()` rendered via `backend.build(...)`, which emits `?` for SQLite and `$1..$N` for Postgres from the same code path. Behavior is otherwise unchanged: the migration remains additive, only fills NULL columns, and stays idempotent. Verified by running the full migration chain against both SQLite and a real PostgreSQL 16 instance via the existing migration test harness.
…series table Surface volume and chapter directly on the native BookDto so listings can classify books without fetching full metadata. The series detail table view gains a "Kind" column rendering a BookKindBadge, and long titles now ellipsis-truncate with a tooltip instead of wrapping across rows. The badge collapses to two semantic colors: - blue = volume only (a complete volume) - grape = chapter (with or without a parent volume — a chapter is a chapter) The Book Info modal mirrors the same Volume/Chapter rows and Classification badge, and reads the fields straight off Book now that they're on BookDto. Tests added for the new badge color rules, the modal classification rows, and the table behavior; existing books/series API integration tests still pass.
Update the MSW mock factories, store, and handlers so the new BookDto volume/chapter and SeriesDto localMaxVolume/localMaxChapter/volumesOwned fields surface in the dev-mode UI: - createBook derives volume/chapter from a series-type heuristic so manga series mix complete volumes and loose chapters in the same series - createSeries seeds the local aggregates and the store recomputes them from the books actually created - toFullBookResponse passes volume/chapter through on both the outer body and the metadata block, and adds chapterLock to the locks block
2d1f16f to
2b8f979
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
series_metadata.total_book_countwith two semantically distinct fields,total_volume_count(Option<i32>) andtotal_chapter_count(Option<f32>), each with its own lock. Fixes the long-standing "109/14" incoherence on chapter-organized series and unblocks per-axis comparisons for release tracking.MetadataApplierwrites with per-field locks/permissions, MangaBaka + AniList plugins populated from provider data, v1 API DTOs, frontend (series detail, manual edit, bulk edit, conditions editor, recommendations), and Komga compatibility (internal field renamed;totalBookCountwire shape preserved for Komic/Mihon).total_book_countcolumn, lock, permission, DTO field, andMetadataWriteTotalBookCountpermission are all dropped in the final phase. Older plugins that still emittotal_book_countround-trip silently (serde drops the unknown field).formatdiscriminator (manga / novel / light_novel / manhwa / etc.) to pluginSearchResultPreview, populated by MangaBaka, rendered as a colored badge inMetadataSearchModal. Fixes the recurring papercut where two visually identical search rows differ only by being the manga vs the novel adaptation.Why now
total_book_countmeant volumes for some users and chapters for others, and there was no way to model a mixed library or a chapter-organized series with a volume-count provider. The release-tracking branch already split the tracking side (series_tracking.latest_known_chapter REAL+latest_known_volume INTEGER); release-tracking Phase 5+ is blocked on metadata catching up. This PR unblocks it.Notable changes
m20260502_000067_split_book_countadds the four new columns and backfillstotal_volume_count(+ lock) fromtotal_book_count(+ lock);m20260502_000068_drop_book_countremoves the legacy columns.down()re-adds them as nullable / default-false with no data restore.PluginSeriesMetadataandUserLibraryEntrylosetotal_book_counton the wire;MetadataWriteTotalBookCountpermission removed; newmetadata:write:total_volume_countandmetadata:write:total_chapter_countpermissions added.total_volume_count; explicit#[serde(rename = "totalBookCount")]keeps the wire format unchanged. No chapter-count field is invented (Komga upstream has none).totalBookCountaccess path is gone; templates and rules using it must switch tototalVolumeCount/totalChapterCount.totalBookCountneed to update.Breaking changes
totalBookCountandtotalBookCountLockare gone from every series-metadata request and response shape (PUT, PATCH, GET, bulk PATCH, recommendations, full metadata, locks).metadata:write:total_book_countremoved.totalBookCountfield-access path removed.