From a44511983dce33d7f8cff64a1ea62b3509447a56 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 11 Jul 2026 16:24:49 +0200 Subject: [PATCH] fix: page through all chapters when the API omits total MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit listAllChapters used `page.total ?? all.length` as the loop bound. The /chapters endpoint always returns `total: null`, so a full 200-row first page broke the loop immediately — the reader dead-ended past chapter 200. Stop on a short page instead; only trust `total` when actually reported. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/Cargo.lock | 2 +- backend/Cargo.toml | 2 +- frontend/package.json | 2 +- frontend/src/lib/api/chapters.test.ts | 17 +++++++++++++++++ frontend/src/lib/api/chapters.ts | 7 +++++-- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f680392..b6679ea 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1558,7 +1558,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "mangalord" -version = "0.128.3" +version = "0.128.4" dependencies = [ "anyhow", "argon2", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 0b54291..603e10d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mangalord" -version = "0.128.3" +version = "0.128.4" edition = "2021" default-run = "mangalord" diff --git a/frontend/package.json b/frontend/package.json index 8154c4c..89eb1dd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "mangalord-frontend", - "version": "0.128.3", + "version": "0.128.4", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/lib/api/chapters.test.ts b/frontend/src/lib/api/chapters.test.ts index b850969..5b96b19 100644 --- a/frontend/src/lib/api/chapters.test.ts +++ b/frontend/src/lib/api/chapters.test.ts @@ -158,6 +158,23 @@ describe('chapters api client', () => { expect(fetchSpy.mock.calls[1][0]).toContain('offset=200'); }); + it('listAllChapters keeps paging when the API omits total (total: null)', async () => { + // The real /chapters endpoint returns `total: null` on every page + // (PagedResponse::new). A full 200-row first page must NOT be mistaken + // for the end — otherwise the reader dead-ends past chapter 200. + const mk = (n: number) => ({ ...chapterFixture, id: `c${n}`, number: n }); + const first = Array.from({ length: 200 }, (_, i) => mk(i + 1)); + const second = Array.from({ length: 50 }, (_, i) => mk(i + 201)); + fetchSpy + .mockResolvedValueOnce(ok({ items: first, page: { limit: 200, offset: 0, total: null } })) + .mockResolvedValueOnce(ok({ items: second, page: { limit: 200, offset: 200, total: null } })); + + const all = await listAllChapters('m1'); + expect(all).toHaveLength(250); + expect(all[249].number).toBe(250); + expect(fetchSpy).toHaveBeenCalledTimes(2); + }); + it('listAllChapters stops after a short final page (single request)', async () => { fetchSpy.mockResolvedValueOnce( ok({ items: [chapterFixture], page: { limit: 200, offset: 0, total: 1 } }) diff --git a/frontend/src/lib/api/chapters.ts b/frontend/src/lib/api/chapters.ts index ce089ac..a282bbc 100644 --- a/frontend/src/lib/api/chapters.ts +++ b/frontend/src/lib/api/chapters.ts @@ -56,8 +56,11 @@ export async function listAllChapters(mangaId: string): Promise { const { items, page } = await listChapters(mangaId, { limit: PAGE, offset }); all.push(...items); offset += items.length; - const total = page.total ?? all.length; - if (items.length < PAGE || all.length >= total) break; + // A short page is the only reliable end-of-list signal: the /chapters + // endpoint returns `total: null`, so we can't infer completion from it. + // Only stop early on `total` when the API actually reports one. + if (items.length < PAGE) break; + if (page.total != null && all.length >= page.total) break; } return all; }