feat(chapter): preserve source-site order in chapter list (0.52.0)
Some checks failed
deploy / test-backend (push) Failing after 11m48s
deploy / test-frontend (push) Successful in 9m45s
deploy / build-and-push (push) Has been skipped
deploy / deploy (push) Has been skipped

The user-facing chapter list ordered by (number ASC, created_at ASC),
which broke the source site's order in two ways: non-numeric entries
("notice. : Officials") parsed to number=0 and clustered at the top,
even though the site placed them mid-list, and variants sharing a
number ("Ch.14 : PH" / "Ch.14 : Official") were torn apart by the
created_at tiebreak.

Capture each chapter's position in the source DOM as `source_index`
(0 = first = newest on this site) on every crawler sync, including the
UPDATE branch so a new chapter prepended on the source shifts every
existing row down by one on the next tick. The list query reverses
this with `ORDER BY source_index DESC NULLS LAST, number ASC,
created_at ASC` so the oldest chapter appears first, variants stay
adjacent in the order the site shows them, and non-numeric entries
land where the site placed them. User-uploaded chapters and pre-
migration rows keep their NULL source_index and fall through to the
prior number/created_at tiebreak via NULLS LAST.

The reader's client-side `[...chapters].sort((a,b) => a.number - b.number)`
is dropped; prev/next now walks the server-ordered array positionally
so it traverses variants and non-numeric entries in display order.

Existing data populates on the next cron tick or via admin force-resync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-03 07:25:09 +02:00
parent b812c6d16c
commit 679abae736
8 changed files with 315 additions and 23 deletions

View File

@@ -33,22 +33,21 @@
);
// Prev/next chapter computed from the chapter list. listChapters
// returns chapters in number ASC order; we still resolve via find
// rather than index because the current chapter's position may
// not be `chapter.number - 1` (sparse numbering / chapter 0.5 /
// future skipped numbers).
const sortedChapters = $derived(
[...chapters].sort((a, b) => a.number - b.number)
);
// returns chapters in display order (reversed source-site order, so
// oldest first — see backend repo::chapter::list_for_manga), and
// prev/next walks that order positionally. Resolving the current
// index via `find` rather than `chapter.number - 1` matters because
// numbers aren't a reliable index: variants share numbers, non-
// numeric entries pin to 0, and uploads can sparse-fill.
const currentIdx = $derived(
sortedChapters.findIndex((c) => c.id === chapter.id)
chapters.findIndex((c) => c.id === chapter.id)
);
const prevChapter = $derived(
currentIdx > 0 ? sortedChapters[currentIdx - 1] : null
currentIdx > 0 ? chapters[currentIdx - 1] : null
);
const nextChapter = $derived(
currentIdx >= 0 && currentIdx < sortedChapters.length - 1
? sortedChapters[currentIdx + 1]
currentIdx >= 0 && currentIdx < chapters.length - 1
? chapters[currentIdx + 1]
: null
);
@@ -471,7 +470,7 @@
}}
data-testid="reader-chapter-select"
>
{#each sortedChapters as c (c.id)}
{#each chapters as c (c.id)}
<option value={c.id}>
{chapterLabel(c)}
</option>