Skip to content

Report scoped imports as destination coverage, and carry want-to-read across - #48

Merged
AshDevFr merged 1 commit into
mainfrom
feat/transfer-report-and-want-to-read
Sep 28, 2026
Merged

AshDevFr merged 1 commit into
mainfrom
feat/transfer-report-and-want-to-read

Conversation

@AshDevFr

Copy link
Copy Markdown
Owner

Follow-up to #47, from the first real import of a library split.

The report read like a failure

Series: 32 matched, 0 ambiguous, 244 unmatched

…for a destination holding 32 series, all of which matched. A split exports the whole old library and imports it into one of several new ones, so most file series are meant to miss. Calling them "unmatched" said the opposite of what happened.

A scoped import now reads:

Series: 32 of the 32 in Shonen matched, 0 ambiguous
Books:  1838 of the 1900 in Shonen matched, 62 missed, …
The file also holds 244 series (3121 books) that belong to other libraries.

Relabel rather than a point-of-view toggle. A toggle makes you go looking for the reassuring view after the alarming one has landed; correct wording means the first reading is right.

Distinct counts. Two file series can resolve to one destination series by name, so counting file matches could report "33 of 32". Coverage counts distinct destination series and books.

Visibility-aware denominator. In-scope totals go through the same filter as matching, so a series hidden from the reader doesn't make an import look incomplete.

Books needed splitting, not just relabelling. booksUnmatched mixes books whose whole series lives elsewhere with books missed inside a series that matched. Labelling all of it "other libraries" would hide real misses behind reassuring wording. New booksInUnmatchedSeries separates them; the page shows the remainder as missed. (This one wasn't in the plan — it surfaced while building the UI.)

Unscoped imports keep the plain counts: there's no destination to measure against, and "unmatched" really does mean nothing was found.

Want-to-read travels with the export

On the series or book it flags, with its original position and queued date.

  • The export seeds queued series and books into its sets. Previously only reading state and ratings did, and a queued series is usually one you never started — so it would never have reached the file.
  • Restored entries follow your existing queue, in original order, and nothing already queued moves. The queue is global but the file is grouped by series and applied one transaction per series, so every entry is ranked by original position once, up front, offset past your current queue. Each transaction writes entries with positions already fixed.
  • The queued date is kept, so newest/oldest sorts still mean what they did.
  • Idempotent: re-importing adds nothing. A failed series rolls its queue entries back with everything else.
  • Checkbox on import, default on.

Not reusing WantToReadRepository::add_series: it needs a plain connection where the importer holds a transaction, stamps the current time, and rescans the whole queue per call.

A stale fixture from #46

The settings test fixture had stayed in snake_case since the wire format became camelCase. tsconfig.app.json excludes *.test.tsx from tsc -b, and nothing asserted a rendered count, so it passed while the component read undefined. Rebuilt as a typed builder; the new tests assert the numbers that render. Worth considering separately whether test files should be type-checked at all.

Tests

Backend: coverage of the destination; distinct counting when two file series land on one; no totals when unscoped; books missed inside a matched series kept apart; a queued-but-unstarted series exported; a queued book exported with its series; restored entries following the existing queue in original order with the original date; re-import adding nothing; the flag off; dry run counting without writing. Plus a unit test pinning queue ranking and tie-breaks across series and books.

Web: scoped wording, the "missed" count staying visible, unscoped wording unchanged, and the checkbox reaching the request in both states.

Verification

  • Backend: 5485 passed, 41 skipped
  • Web: 3721 passed across 229 files
  • cargo clippy --all-targets -- -D warnings exit 0, tsc -b clean, biome clean
  • OpenAPI spec and TypeScript types regenerated

Additive fields only, so the document format stays at version 1: older files import with the fields absent, and the document has no deny_unknown_fields.

…cross

The first real import of a split read like a failure: "32 matched, 244
unmatched", for a destination that holds 32 series, every one of which
matched. A split exports the whole old library and imports it into one of
several new ones, so most file series are meant to miss, and calling them
unmatched said the opposite of what happened.

A scoped import now reports coverage of the destination: how many series and
books the selected libraries hold, and how many of those received state. The
matched side counts distinct destination series and books, because two file
series can resolve to one destination series by name and counting file
matches can report more than the destination holds. The totals go through
the same visibility filter as matching, so a series hidden from the reader
does not make the import look incomplete. Unscoped there is no destination
to measure against, so the plain counts stay and "unmatched" keeps meaning
nothing was found.

The book count needed splitting, not just relabelling. `booksUnmatched` mixes
books whose whole series belongs elsewhere with books missed inside a series
that did match. Calling all of it "other libraries" would have hidden real
misses behind the reassuring wording, so the summary counts the first group
separately and the page shows the remainder as missed.

Want-to-read now travels in the file, on the series or book it flags. The
export seeds queued series and books into its sets: a queued series is usually
one never started, and with no reading state nothing else would have put it
in the file. On import the queue is global but the file is grouped by series
and applied a series at a time, so every entry is ranked by its original
position once, up front, and offset past the reader's existing queue. Each
series transaction then writes entries whose positions are already fixed, so
restored entries follow the existing queue in their original order, and
nothing already queued moves. The original queued date is kept, so the newest
and oldest sorts still mean what they did. A checkbox turns it off.

The settings test fixture had stayed in snake_case since the wire format
became camelCase. Test files are excluded from the type check and nothing
asserted a rendered count, so it passed while the component read undefined.
It is rebuilt as a typed builder, and the new tests assert the numbers that
render.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying codex with  Cloudflare Pages  Cloudflare Pages

Latest commit: c2747fb
Status: ✅  Deploy successful!
Preview URL: https://893ac7cc.codex-asm.pages.dev
Branch Preview URL: https://feat-transfer-report-and-wan.codex-asm.pages.dev

View logs

@github-actions

Copy link
Copy Markdown

API contract changes

Compared against main. These are changes a client generated from the
previous document would notice. Not a failure: breaking changes are a
release-time decision, and make release-prepare checks the bump against
them when the version is chosen.

Report
No breaking changes

@AshDevFr
AshDevFr merged commit 87fe9b3 into main Sep 28, 2026
24 checks passed
@AshDevFr
AshDevFr deleted the feat/transfer-report-and-want-to-read branch September 28, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant