Skip to content

Merge the Alternate Number sort into the Alternate Series sort - #832

Merged
ajslater merged 4 commits into
developfrom
merge-alternate-number-sort
Aug 30, 2026
Merged

Merge the Alternate Number sort into the Alternate Series sort#832
ajslater merged 4 commits into
developfrom
merge-alternate-number-sort

Conversation

@ajslater

Copy link
Copy Markdown
Owner

What

The two half-working sorts for alternate series are now one key: Alternate Series (reprints). Comics group by their alternate series identity (name, volume, language) and order by the parsed AlternateNumber inside the group — #2 before #10 — falling back to the comic's own series and issue so untagged comics interleave instead of clumping. alternate_number is removed from the order-by choices with a full stored-settings remap. Matches the reader, which already reads alternate series in parsed-number order.

Why

Previously each sort did half the job: Alternate Series ordered by the display label, so "#10" sorted before "#2" and fallback comics had no issue tail; Alternate Number sorted numerically but only resolved which reprint via the reprints filter, silently degraded to the plain issue sort without it, and never considered the series name. The merge also fixes two latent bugs: the label sort compared case-sensitively (SQLite collation doesn't survive Coalesce/Concat/Min), and the filtered number sort aggregated number and suffix independently, so a comic with reprints "#2" and "#10a" sorted by a fabricated "#2a".

How

  • Comic rows (columns.py): a correlated subquery elects one reprint per comic by natural tuple order; ORDER BY is a five-field Coalesce list reading from that election. All five subqueries share one election ordering, so tied rows carry identical values — the tuple stays atomic without a composed string key. An active reprints filter narrows the election (pk__in); no more FilteredRelation. Benchmarked at parity with the join+aggregate shape (164 vs 167 ms sorting 120k comics; the forced GROUP BY pk keeps the correlated subqueries out of GROUP BY). The fallback series segment is lower(name) rather than the article-moved sort_name, so an untagged "The Batman" interleaves with its "The Batman" alternate namesake.
  • Collection rows (intersections.py): the intersection RawSQL emits a matching fixed-width key with an in-SQL fallback to the collection's own name (a Django Coalesce wrapper would re-trigger the quadratic Folder GROUP BY regression). The key reads only reprint columns — a per-comic fallback under GROUP BY target_id returned an arbitrary child's issue — and its printf formats are %%-escaped so the DEBUG query logger (bin/dev.sh default) doesn't consume bound params. Both fixes have regression tests.
  • Settings: 0053's choices tuple edited in place (unreleased); new pure-RunPython 0054 remaps alternate_number → reprints in order_by, order_extra_keys, and collection_order_memory with dedupe (forward-only; the merge is lossy). Sidecar restore gets a separate sort-key rename map covering the same three homes — kept apart from _LEGACY_KEY_RENAMES, which also drives filter-column resolution.
  • Frontend: the alternate_number caption branch is gone; formatReprints and the order_value display contract are unchanged.

Reviewer notes

  • Accepted behavior changes (called out in NEWS): saved "Alternate Number" sorts become "Alternate Series" with series-grouped rather than purely numeric ordering; a collection's cover pick no longer follows the alternate numbering (the merged key normalizes to sort_name in cover subqueries like every other M2M sort); the reprints filter no longer influences collection-row ordering.
  • reprints stays a legal multi-sort extra; Comic.alternative_issue_number is an unrelated display-only field and is untouched.
  • Tests: 23 in test_browser_reprints_column.py including cross-series election atomicity, case folding, article interleave, and a DEBUG-cursor regression; 0054 remap and restore-rename suites. The subtle guarantees were mutation-tested (each fails a dedicated test when broken). Full suite 1032 passed; lint/ty/complexity clean.

🤖 Generated with Claude Code

ajslater and others added 4 commits August 30, 2026 12:32
One sort key now does both halves: comics group by their alternate
series (ComicInfo AlternateSeries / MetronInfo reprints) and order by
the parsed AlternateNumber inside the group, falling back to the
comic's own series and issue so untagged comics interleave.

A correlated subquery elects one reprint per comic by natural tuple
order and ORDER BY is a five-field Coalesce list reading from that
election — numbers compare numerically, and the shared election
ordering keeps the tuple atomic (the old sort aggregated number and
suffix independently and could pair parts from different reprints).
An active reprints filter narrows the election instead of using a
FilteredRelation. Benchmarked at parity with a join+aggregate at 120k
comics; the fallback series segment uses lower(name) so article-led
titles interleave with their alternate namesakes.

Collection rows sort by a matching fixed-width key in the intersection
RawSQL, with an in-SQL fallback to the collection's own name. The key
reads only reprint columns (a per-comic fallback under GROUP BY came
back from an arbitrary row) and its printf formats are %%-escaped so
the DEBUG query logger doesn't consume bound params.

alternate_number is removed from the order-by choices (0053 edited in
place, unreleased). New 0054 remaps stored settings in order_by,
order_extra_keys and collection_order_memory with dedupe; sidecar
restore gets a sort-key rename map covering the same three homes.
Cover-pick subqueries for the merged key normalize to sort_name like
every other M2M sort.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ajslater
ajslater merged commit afb6278 into develop Aug 30, 2026
3 checks passed
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