Sort and read by alternate series (#360) - #830
Merged
Merged
Conversation
ComicInfo AlternateSeries / AlternateNumber already imported into the Reprint model, but there was no way to order by the alternate numbering and no way to read an alternate series through in order. Alternate series tags make durable reading lists — they survive a re-import, rename or re-tag — which is what issue #360 asks for. Add an "Alternate Number" sort. Reprint.issue is a string, so "#10" sorted before "#2"; split it into issue_number / issue_suffix columns mirroring Comic's, derived in presave and backfilled by migration. The sort scopes to the alternate series picked by the reprints filter via a FilteredRelation, the way story_arc_number scopes to the browsed arc. Comics carrying no alternate number fall back to their own issue number so a mixed listing stays readable rather than collapsing to NULL. Surface the existing "Alternate Series" sort in cover view too, and sort it through a Coalesce onto the real series sort_name so untagged comics interleave instead of clumping under an empty aggregate. Let the reader follow an alternate series as a reading order. An alternate series is identified by (series_name, volume_number, language) — the Reprint unique key minus the issue — and the arc handle is the whole group's pks, since each issue is its own Reprint row and a per-comic handle would change from book to book. Reprint rows join the TimestampUpdater re-stamp flow and the mtime probe accepts them, so an open reader still notices a re-import. Also fixes a latent error: an M2M primary sort outside table view (cover cards, OPDS) emitted an ORDER BY alias that was only annotated in table mode. Unreachable through the UI before, but surfacing the Alternate Series sort in cover view makes it a normal path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CrB4hZ7HtGE9YjV5EkXeQ3
ajslater
marked this pull request as ready for review
August 30, 2026 06:51
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.
Closes #360.
ComicInfo
AlternateSeries/AlternateNumberalready import into theReprintmodel, and there's already an "Alternate Series" table column, filter and full-text search field. What was missing is the two things the issue actually asks for: ordering by the alternate numbering, and reading an alternate series through in order. Alternate series tags make durable reading lists — they survive a re-import, rename or re-tag — which is the requester's use case.Alternate Number sort
Reprint.issueis a string, so#10sorted before#2. Split it intoissue_number/issue_suffixcolumns mirroringComic's, derived inpresave()and backfilled by migration0053.Reprint.save()now callspresave()(the importer's bulk paths already did).The sort scopes to the alternate series picked by the
reprintsfilter through aFilteredRelation, the same waystory_arc_numberscopes to the browsed arc — there's no alternate-series browse collection, so the filter is the only context. Comics carrying no alternate number fall back to their own issue number (coalesced inside the aggregate, so each comic contributes its own effective value) rather than collapsing to NULL. With no filter at all the key degrades to the plain issue sort.Alternate Series sort in cover view
Added to the cover-view sort dropdown. Comic rows now sort through a
Coalesceonto the real seriessort_name, so comics with no alternate series interleave by their actual series instead of clumping under an empty aggregate. The table cell still renders the full JSON label list — the fallback is a sort-only annotation.Reader reading order
The reader offers an alternate series as a reading order; next/prev follow
AlternateNumber(number, then suffix, then date). An alternate series is identified by(series_name, volume_number, language)— theReprintunique key minus the issue — so a v1 and a v2, or an English and a Spanish edition, stay separate reading orders.The arc handle is the whole group's Reprint pks, not just the current comic's: each issue of an alternate series is its own
Reprintrow, so a per-comic handle would report different ids on the next book and silently drop the reading order.Reprintrows join theTimestampUpdaterre-stamp flow and the mtime endpoint accepts reprint arcs, so an open reader still notices a re-import.reprintsis a reader-only pseudo-collection, deliberately not aCollectionenum member — those maps are exhaustive over the enum and assume a browse route and a cover exist.No new browse collection and no OPDS navigation changes, per the agreed scope.
Incidental fix
An M2M primary sort outside table view (cover cards, OPDS feeds) emitted an ORDER BY alias that was only annotated in table mode, raising a
FieldError. It was unreachable through the UI before, but surfacing the Alternate Series sort in cover view makes it a normal path, so the annotation gate is fixed here.Testing
tests/test_browser_reprints_column.py— numeric ordering (2, 3, 10 where a string sort gives 10, 2, 3), reverse, suffix tiebreak, fallback to issue number, degradation with no filter, collection-row aggregation, cover-view label sort, series-name fallback, and multi-sort-extra alias resolution.tests/test_reader.py— the arc is offered and named, the handle covers the whole group, prev/next follow 2 → 3 → 10, arc selection survives the next book, an absent alternate series falls back rather than erroring, the mtime probe accepts the arc, andTimestampUpdaterre-stamps reprints.frontend/tests/unit/order-by-caption.test.js,frontend/tests/unit/reader-arc-select.test.js.ruffandtyclean. Frontend: 437 passed;stats-tab.test.jsfails identically on unmodifieddevelop(pre-existing, unrelated).Frontend tests need Node 24 — this container's Node 22 lacks
Intl.DurationFormat, whichsrc/datetime.jsconstructs at import; I ran them behind a local-only shim.Generated by Claude Code