From 8f79e9205331de6c32bbba3a145bb111f677f811 Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:37:54 -0600 Subject: [PATCH] feat(hardcover): add per-book sync controls and book sync scope Introduce a per-book sync toggle in the book detail view that lets users include or exclude individual books from Hardcover sync. Add a book sync scope setting with two modes: - all_eligible: sync all books unless explicitly excluded (default) - selected_only: sync only books the user explicitly includes Extract sync eligibility logic into hardcover-sync-policy.ts for testability. The toggle handles unread books correctly: sync is skipped while the book is unread, but the user's inclusion intent is stored so sync activates automatically once reading starts. Closes #412 --- .../components/detail/tabs/DetailsTab.vue | 6 +- .../detail/tabs/__tests__/DetailsTab.spec.ts | 17 +- .../api/__tests__/hardcover.api.spec.ts | 115 +- .../features/hardcover/api/hardcover.api.ts | 31 + .../components/HardcoverBookSyncGridItem.vue | 30 + .../components/HardcoverConnectionCard.vue | 43 +- .../components/HardcoverSyncProgress.vue | 5 +- .../HardcoverBookSyncGridItem.spec.ts | 484 + .../__tests__/HardcoverConnectionCard.spec.ts | 201 + .../__tests__/HardcoverImportStatus.spec.ts | 1 + .../__tests__/HardcoverSyncProgress.spec.ts | 120 + .../__tests__/useHardcoverSettings.test.ts | 1 + .../__tests__/useHardcoverSync.test.ts | 47 + .../composables/useHardcoverBookSyncState.ts | 172 + packages/types/src/hardcover.ts | 30 + .../0023_add_hardcover_sync_controls.sql | 3 + .../src/db/migrations/meta/0023_snapshot.json | 10955 ++++++++++++++++ server/src/db/migrations/meta/_journal.json | 7 + server/src/db/schema/hardcover.ts | 3 + .../modules/hardcover/dto/hardcover.dto.ts | 11 +- .../hardcover-settings.service.test.ts | 22 +- .../hardcover/hardcover-settings.service.ts | 7 + .../hardcover/hardcover-sync-policy.test.ts | 139 + .../hardcover/hardcover-sync-policy.ts | 69 + .../hardcover/hardcover-sync.service.test.ts | 223 +- .../hardcover/hardcover-sync.service.ts | 154 +- .../hardcover/hardcover.controller.test.ts | 97 +- .../modules/hardcover/hardcover.controller.ts | 30 +- .../src/modules/hardcover/hardcover.module.ts | 3 +- .../hardcover/hardcover.repository.test.ts | 20 +- .../modules/hardcover/hardcover.repository.ts | 36 +- 31 files changed, 13019 insertions(+), 63 deletions(-) create mode 100644 client/src/features/hardcover/components/HardcoverBookSyncGridItem.vue create mode 100644 client/src/features/hardcover/components/__tests__/HardcoverBookSyncGridItem.spec.ts create mode 100644 client/src/features/hardcover/components/__tests__/HardcoverConnectionCard.spec.ts create mode 100644 client/src/features/hardcover/components/__tests__/HardcoverSyncProgress.spec.ts create mode 100644 client/src/features/hardcover/composables/useHardcoverBookSyncState.ts create mode 100644 server/src/db/migrations/0023_add_hardcover_sync_controls.sql create mode 100644 server/src/db/migrations/meta/0023_snapshot.json create mode 100644 server/src/modules/hardcover/hardcover-sync-policy.test.ts create mode 100644 server/src/modules/hardcover/hardcover-sync-policy.ts diff --git a/client/src/features/book/components/detail/tabs/DetailsTab.vue b/client/src/features/book/components/detail/tabs/DetailsTab.vue index ab6b81d4d..63a92c2b4 100644 --- a/client/src/features/book/components/detail/tabs/DetailsTab.vue +++ b/client/src/features/book/components/detail/tabs/DetailsTab.vue @@ -49,6 +49,7 @@ import { useKoreaderBookProgress } from '@/features/koreader/composables/useKore import { RATING_STARS, getRatingStarClass } from '@/features/book/lib/rating-stars' import BookCoverSurface from '@/features/book/components/BookCoverSurface.vue' import { useDisplaySettings } from '@/composables/useDisplaySettings' +import HardcoverBookSyncGridItem from '@/features/hardcover/components/HardcoverBookSyncGridItem.vue' type FileProgress = { percentage: number @@ -92,7 +93,9 @@ const showSendDialog = ref(false) const { weights: scoreWeights, fetchWeights } = useMetadataScoreWeights() const { bookProgress: koreaderBookProgress, fetchBookProgress: fetchKoreaderProgress } = useKoreaderBookProgress() -onMounted(fetchWeights) +onMounted(() => { + void fetchWeights() +}) const { pendingId: deleteBookId, @@ -1650,6 +1653,7 @@ watch(
-
+
Date Started