mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
feat(metadata): add hardcover edition selection and refresh pinning (#936)
* feat(metadata): add hardcover edition selection and refresh pinning Mirrors the StoryGraph edition picker for Hardcover, but backed by real GraphQL edition data instead of scraping. An edition can now be chosen either from the Hardcover settings page's new "Linked books" section or from the metadata downloader/refresh flow, and once chosen it pins future automatic metadata refreshes to that exact edition instead of silently re-deriving one by ISBN every time. book_metadata.hardcover_edition_id is shared across all users, while hardcover_book_state.hardcover_edition_id is scoped per (user, book). Cross-writing between them is one-directional and non-destructive per flow: the settings page always updates the acting user's own sync state and only back-fills the shared field if it is empty; the metadata downloader always updates the shared field and only updates the acting user's own sync state if they already have one. The propagation uses the existing achievement event bus rather than a direct module import, since HardcoverModule already depends on BookModule. * fix(metadata): address hardcover edition selection review findings Responds to CodeRabbit review on PR #936: - Surface Hardcover edition-lookup failures as errors instead of an empty list, so a provider outage cannot be mistaken for "no editions". Updates the client to throw and the UI to show a retryable error state. - Validate that a manually-picked edition id actually belongs to the book's matched Hardcover book before persisting it, rejecting mismatches with a 400 instead of trusting an unvalidated client input. - Require an exact safe-integer string before parsing a propagated hardcoverEditionId, so a value like "200abc" cannot be silently truncated into a valid-looking edition id. - Add durationMs to the edition-propagation failure log per the project's logging conventions. - Honor a pinned edition in the ISBN search() path too, not only in lookupById(), so a refresh that falls back to search still returns the previously chosen edition when it's present. * test(metadata): strengthen hardcover edition-pin fallback assertions The fallback tests for search() and lookupById() only used a single-edition fixture, so they would have passed even if the edition-pin filtering never ran. Use two-edition fixtures and assert on which edition is actually selected instead. * i18n(hardcover): use i18n keys for Linked books strings HardcoverLinkedBooks.vue hardcoded its UI text instead of using useI18n like its sibling components, breaking the ability to translate the Hardcover settings page and non-English catalogs. * fix(hardcover): harden edition selection and bound the linked-book list Validate a picked edition against the book it belongs to rather than against a capped, unordered list. Editions past the cap stay selectable, and two calls can no longer disagree about which window they saw. - Order the edition query by id and fetch one row past the cap, so truncation is detectable instead of silent. Verified against the live Hardcover API: both "The Name of the Wind" and "Pride and Prejudice" exceed the 50 cap. - Report truncation for the linked-book list and the edition list, and say so in the UI rather than presenting a partial list as complete. - Bound and order the linked-book query. It backed a settings list with the unbounded sync query; sync callers keep their previous unordered behaviour. - Throw for "not connected" and "not matched" instead of returning an empty list, which the UI could not tell apart from a genuinely empty catalogue. - Drop release_year from the edition query. Hardcover never populates it, so it was dead weight in the payload. - Add a tuned --success token. The status pill hardcoded text-green-600, which is untuned in dark mode; the light value is set for 4.5:1 on card and page. - Expose row expansion with aria-expanded and aria-controls, announce load failures through a live region, and keep the edition list open after a switch so the refreshed marker lands where the user is looking. --------- Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
This commit is contained in:
@@ -135,3 +135,47 @@ export interface HardcoverImportApplyResult extends HardcoverImportSummary {
|
||||
progressApplied: number;
|
||||
failed: number;
|
||||
}
|
||||
|
||||
export interface HardcoverEdition {
|
||||
id: number;
|
||||
title: string | null;
|
||||
format: string;
|
||||
pages: number | null;
|
||||
isbn10: string | null;
|
||||
isbn13: string | null;
|
||||
publisher: string | null;
|
||||
language: string | null;
|
||||
publishedDate: string | null;
|
||||
coverUrl: string | null;
|
||||
}
|
||||
|
||||
export interface HardcoverLinkedBook {
|
||||
bookId: number;
|
||||
title: string | null;
|
||||
authorName: string | null;
|
||||
hardcoverBookId: number | null;
|
||||
hardcoverEditionId: number | null;
|
||||
matchMethod: string | null;
|
||||
matchError: string | null;
|
||||
}
|
||||
|
||||
// Both list results are capped server-side; `truncated` lets the UI say so instead of
|
||||
// presenting a partial list as the whole thing.
|
||||
export interface HardcoverLinkedBooksResult {
|
||||
books: HardcoverLinkedBook[];
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
export interface HardcoverEditionsResult {
|
||||
editions: HardcoverEdition[];
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
export interface SetHardcoverEditionPayload {
|
||||
editionId: number;
|
||||
}
|
||||
|
||||
// Every failure path throws, so a resolved response always means the edition was applied.
|
||||
export interface SetHardcoverEditionResult {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user