A provider identifier parsed out of an OPF was passed on verbatim, so a Calibre
`opf:scheme="AMAZON"` holding a product URL reached a varchar(20) column as a 36-character
value. It could not be stored, and the next manual save of that book was rejected for a field
the user never typed into. Drop an identifier that cannot fit its column rather than truncating
it: an overflowing value is not a long identifier, it is a different kind of value, and its
first 20 characters are not an ASIN either. Enforce the same bound where every extractor
converges, so no parser can fail a metadata write with a Postgres 22001.
Keep the server's description of a rejected save instead of discarding the response body. Only
that text names the field at fault, so the editor showed a bare "HTTP 400" and left the reason
in the network tab. It stays untranslated, so it is preferred over the catalog message rather
than replacing it, and the failure is carried as a structured value so the component still
translates everything else.
Tie the DTO limits to the column widths they were copied from, and guard the pair with a test,
since nothing connected them when they drifted apart.
Stand Node's built-in web storage down in the client test run. Node 26 defines localStorage on
globalThis, jsdom shares that object, and its getter reads as undefined without
--localstorage-file, which failed 229 tests across 28 unrelated suites on CI.
Closes#1015
Author, narrator, genre and tag names written before whitespace normalization existed can hold
a non-breaking space, tab, newline or doubled space. HTML collapses all of those, so the name
renders correctly in the UI while the ILIKE search predicate never matches it; unaccent() does
not touch whitespace either. Re-entering an identical name by hand fixed a single book because
the write path canonicalizes the row it reuses, but nothing ever ran for untouched books.
Collapse stored names to the form normalizeMetadataTextSql() produces, merging rows that collide
once collapsed and remapping every junction and content-filter reference before a duplicate is
dropped. Case variants are left alone, since search is already case-insensitive. Close the two
write paths that still produced un-collapsed names, and normalize the typed search term so a
pasted non-breaking space still matches.
Also carries staged client appearance work: the surface brightness picker and related controls.
Closes#1014
Record how many books a series holds from Hardcover, ComicVine, RanobeDB,
Amazon and embedded ComicInfo metadata, storing it on the series row. Show
an ownership meter beside read progress on series detail, and extend gap
detection to report entries past the highest owned index instead of stopping
there. Allow the total to be set by hand in the book metadata editor.
Automated sources only ever raise the stored total so the result does not
depend on scan order; the manual field is the way to lower or clear it.
Closes#801
* fix(metadata): extract and write back comicvine id for comics
ComicVine references were never read from a comic's embedded
ComicInfo.xml on import, even though every other provider id
(Goodreads, Amazon, Hardcover, Google Books, OpenLibrary, Kobo)
already round-trips through the same <Web>/Notes fields. This
affected Book Dock import, direct upload, and library scanning
alike, since they share the same extractor. Write-back had the
same gap: a comicvineId resolved via search/auto-fill was never
persisted back into the file.
Closes#840
* fix(metadata): keep comicvine id off the contested Web slot and source-check notes
ComicInfo has a single Web element and buildComicInfoXml writes the first
provider id in COMIC_INFO_PROVIDER_ID_KEYS order into it. The other six
providers are also in COMIC_INFO_MANAGED_NOTES_KEYS, so losing that slot costs
them nothing; comicvineId had no second channel, so a write-back for a book
that also carried a goodreads/amazon/hardcover/google/openlibrary/kobo id
dropped the id from the file entirely and clobbered any ComicVine URL that was
already there. Add comicvineId to the managed notes keys, which also makes the
existing managed-note read precedence reachable from our own writes.
ComicTagger emits "[Issue ID N]" for whichever talker produced the tag, naming
the source separately as "using info from <source>". Matching the bare marker
attributed Metron and other non-ComicVine issue ids to ComicVine and wrote them
back out as fabricated comicvine.gamespot.com URLs. Require the Comic Vine
source in the same sentence.
Also anchor the Web URL scan to a path segment so a segment ending in 4000-,
such as "14000-777", is no longer read as an issue id.
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Ensure metadata score calculations are awaited when book metadata or covers are saved or updated, and add concurrency queuing for on-demand score recalculations.
Pass external provider IDs (including Hardcover and Open Library IDs) from book dock metadata selection through ingest and finalization so they are saved to book metadata on import.
Closes#704
- Add a persisted publication date while continuing to derive publication years.
- Thread full dates through metadata import, editing, filters, sorting, write-back, and sync payloads.
- Cover provider parsing, DTO validation, jump buckets, and date formatting with tests.
Closes#596
Narrator names and sort names are now normalized to remove duplicate
internal whitespace and leading/trailing spaces. Names are deduplicated
case-insensitively when saved to prevent duplicate metadata entries that
are visually identical or very similar in the UI.
Closes#553
Adds hardcoverEditionId to the database schema and shared types, enabling it to be stored as first-class metadata. Updates the file metadata parsers and writers (EPUB, PDF, CBZ) to read and embed this ID, and exposes it in the frontend metadata editor.
Closes#420
Update cover mutation paths to bump the book timestamp used for cover cache busting, and carry per-book collapsed-series cover versions so child covers do not reuse a representative book timestamp.
* feat(metadata): add Aladin Korean book metadata provider
Add support for fetching Korean book metadata from Aladin OpenAPI.
Includes search by title/author/ISBN and lookup by ISBN-10/13.
- Add ALADIN to MetadataProviderKey in @bookorbit/types
- Add AladinProviderConfig to ProviderConfigurations
- Create AladinProvider with IdentifiableProvider interface
- Add mapper for Aladin API response to MetadataCandidate
- Register provider in MetadataFetchModule
- Add provider config merge, validation, and connection test
- Include 24 unit tests (provider + mapper)
* feat(client): add Aladin provider UI integration
* refactor(metadata): use 'Aladin' label and favicon-based icon
* feat(metadata): complete Aladin provider end-to-end wiring
Persist the Aladin ItemId across fetch, scoring, locks, and file metadata round-trip, and update the affected tests.
Co-authored-by: Kyungwook Park <288273+kyungw00k@users.noreply.github.com>
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Co-authored-by: Kyungwook Park <288273+kyungw00k@users.noreply.github.com>
This introduces a many-to-many relationship between books and series, resolving the previous limitation where a book could only belong to a single series. It updates the database schema, query builders, server DTOs, and the Vue client interface to manage multiple series memberships per book.
Closes#353
The upload paths saved only the aggregate book_metadata duration, leaving book_files.durationSeconds
null so the player summed to zero. Extract and aggregate per-file duration on new uploads and added
audio files, matching the scanner.
Ref #330
* feat(metadata): add LubimyCzytac metadata provider
- Add LubimyCzytac scraper and provider implementation
- Add lubimyczytacId field across types, schema, extractors, and metadata service
- Register lubimyczytac provider in module, pipeline, repository, and config service
- Add provider icon, color, link, and UI support in book detail views
- Add migration for lubimyczytac_id column
Closes#304
* fix(books): keep cover card read button opening reader when thumbnail click prefers details
The overlay primary button should always open the reader regardless of
the thumbnailClickAction setting. That setting only controls card click
behaviour.
* fix(books): add explicit read button to hover overlay when thumbnail click prefers details
When thumbnailClickAction is 'details', a dedicated Read button now
appears in the overlay top row so users can still open the reader
directly without changing their preference.
Add server-backed jump buckets so large library, collection, and smart-scope listings can jump directly to sorted sections without loading every row up front.
Closes#163
Add Kobo as a first-class metadata source so books can keep Kobo IDs across fetch, edit, import, scoring, and file-write flows. Provide the Python cloudscraper setup needed for production fetches while keeping local setup explicit.
Closes#247
Preserve RanobeDB IDs through write-back, file import, and load-from-file so saved metadata survives round trips.
Expose saved RanobeDB sources in the book detail surfaces with the provider icon and database link.
Closes#207Closes#210
- Extend file write pipeline to write metadata fields directly into audiobook files.
- Map metadata fields to standard audio tag mappings and run ffmpeg to embed tags and cover art for M4B, M4A, MP3, and FLAC files.
- Update UI in edit metadata tab to show file write status and highlight written fields.
Handle edge cases in metadata extraction, file write-back status checks, and comic XML building. This ensures narrators are not incorrectly populated when they match the author/artist, preserves audibleId during chapter extraction, prevents whitespace issue numbers from breaking the XML, and improves the UI to clearly show why write-back is disabled.
Transition from title-based to ID-based routing for series to prevent
404 errors when a series has an excessively long title. This introduces
numeric IDs for series, backed by schema updates, and updates the
frontend router to use seriesId instead of seriesName.
Closes#198
- add audiobook reader with playback queue, progress tracking, and audio settings
- add narrator support with DB schema, module, and metadata extraction
- add Audible and Audnexus metadata providers with mappers and tests
- improve scanner with file classification, walk utilities, and file-event processing
- extend upload validator and processor for audio formats
- add CODEOWNERS assigning .github/ to @neonsolstice
- configure automated updates for npm, docker, and github-actions ecosystems
- replace custom change-detection bash script with dorny/paths-filter@v4
- consolidate lint, typecheck, and test into a single checks matrix job
- gate per-package change detection via has-changes step (matrix unavailable in job-level if)
- fix duplicate actions/setup-node call in setup-node-pnpm composite action
- scope pnpm store cache key by pnpm version to prevent stale cross-version cache hits
- pin all action references to commit SHAs
- fix container-image concurrency group to use commit SHA across all trigger types
- add timeout-minutes to all jobs
- document fail-fast: false rationale and checks: write trade-off
- add cover-fb2.ts: extract cover from <coverpage>/<binary> in FB2 XML
- add fb2-parser.ts: extract title, authors, series, language, year, genres from FB2 title-info
- wire FB2 into cover.ts extractor switch and metadata.service.ts extractMetadata
- wire FB2 into staging-metadata.service.ts for staging ingestion
- add fb2 to METADATA_FORMATS in scanner.service.ts and upload-processor.service.ts
- add MetadataService.saveExtractedCoverBytes() for saving cover bytes directly
- fix staging finalize: copy staging cover to book cover dir on finalize, overwriting stale covers
- add Regenerate Cover button to CoverEditorPanel with permission gate and cache busting