Novel userscript's client-side latest-chapter scan is still scoped to the derived slug (lnw) #91

Closed
opened 2026-08-11 11:54:13 +07:00 by sulthan · 2 comments
Owner

Follow-up from #89 (part of #80 / #77).

lightnovelworld.latestChapterFromAnchors(anchors, seriesId) in userscript/novel-bookmark.user.js builds lightnovelworld\.net/<seriesId>-chapter-([0-9.]+)/ — the same scoping the backend Poll is dropping in #87, but on the client side.

Since #89, seriesId is the Series slug read from the page pointer, not the Chapter Slug. On a divergent novel the two differ (immortality-simulator vs chapter addresses under my-longevity-simulation), so the client-side scan matches zero anchors and the background latest-chapter check yields nothing for exactly the novels this work exists to fix. On the split novel it happens to still work, because the higher range is published under the Series slug.

Impact is limited: the backend Poll is the authority for Latest Chapter, and it will be unscoped after #87. The client-side check is an optimisation that refreshes between polls, so the failure mode is staleness between polls, not a wrong value.

Deliberately left out of #89's scope rather than smuggled in. The fix likely mirrors #87: match any chapter-shaped address on the host, with whatever the client-side equivalent of the wpd-threads truncation needs to be — the userscript scans anchors it has already extracted, so whether the comment-poisoning risk applies there needs checking before copying the backend's approach.

Follow-up from #89 (part of #80 / #77). `lightnovelworld.latestChapterFromAnchors(anchors, seriesId)` in `userscript/novel-bookmark.user.js` builds `lightnovelworld\.net/<seriesId>-chapter-([0-9.]+)/` — the same scoping the backend Poll is dropping in #87, but on the client side. Since #89, `seriesId` is the Series slug read from the page pointer, not the Chapter Slug. On a divergent novel the two differ (`immortality-simulator` vs chapter addresses under `my-longevity-simulation`), so the client-side scan matches zero anchors and the background latest-chapter check yields nothing for exactly the novels this work exists to fix. On the split novel it happens to still work, because the higher range is published under the Series slug. Impact is limited: the backend Poll is the authority for Latest Chapter, and it will be unscoped after #87. The client-side check is an optimisation that refreshes between polls, so the failure mode is staleness between polls, not a wrong value. Deliberately left out of #89's scope rather than smuggled in. The fix likely mirrors #87: match any chapter-shaped address on the host, with whatever the client-side equivalent of the `wpd-threads` truncation needs to be — the userscript scans anchors it has already extracted, so whether the comment-poisoning risk applies there needs checking before copying the backend's approach.
sulthan added the needs-triage label 2026-08-11 11:54:13 +07:00
Author
Owner

This was generated by AI during triage.

Problem Statement

A Reader tracking a lightnovelworld novel whose chapters are published under more than one Chapter Slug sees the userscript's own latest-chapter check contribute nothing. Since #89 the stored series_id is the Series slug read from the page pointer, while chapter addresses carry a Chapter Slug; the client scan scopes its anchor match to the stored slug, so on exactly the divergent novels this line of work exists to fix it matches zero anchors and yields no Latest Chapter.

The failure is silent and the value is never wrong — the backend Poll is the authority for Latest Chapter and #87 already unscoped it there. What the Reader gets is staleness between polls, on one Site, on the subset of novels that were already the hardest case.

Solution

Remove the client-side latest-chapter scan for lightnovelworld rather than porting #87's fix to it. The Poll refreshes a lightnovelworld Series roughly four times more often than the client ever can (poll cooldown one hour, plain-TLS fetcher, no per-navigation gating; client throttle four hours, one Series per navigation), so the client scan on this Site is an optimisation that optimises nothing while carrying a shared-row poisoning risk that fixing it would require defending.

novelfull's client scan is untouched, as are all four manga Sites.

User Stories

  1. As a Reader tracking a divergent lightnovelworld novel, I want the Latest Chapter I see to come from the Poll, so that the number is right whether or not the Series publishes under one Chapter Slug or several.
  2. As a Reader tracking any lightnovelworld novel, I want a New Chapter to appear within a poll cooldown, so that the ember means what it says without the userscript needing to look for itself.
  3. As a Reader browsing lightnovelworld, I want the userscript to stop fetching a megabyte-scale Series page in the background, so that navigating the Site is not slowed by work the backend already did an hour ago.
  4. As a Reader whose novels are shared with other Readers on the same row, I want no stranger's comment on a Series page to be able to set the Latest Chapter, so that a public comment box cannot move a value my library depends on.
  5. As a Reader standing on a lightnovelworld Series page, I want the page never to write a Latest Chapter derived from the comment thread below the chapter list, so that the one path that reads the live DOM is as safe as the Poll.
  6. As a Reader tracking a novelfull novel, I want the background check to keep working exactly as it does today, so that removing one Site's scan does not cost me another Site's freshness.
  7. As a Reader tracking manga, I want the manga userscript untouched, so that a novel-side change cannot regress Asura, Demonic, comix or kagane.
  8. As a Reader on lightnovelworld, I want the Bookmark button, progress recording, dwell timer and stale-row repair to behave exactly as before, so that only the latest-chapter check changes.
  9. As a Reader who is offline, I want the removal to change nothing about queueing and replay, so that a parked write still lands when I come back.
  10. As a maintainer, I want the comment-thread truncation invariant to live in exactly one place, so that a Site redesign is a one-file fix rather than two copies drifting apart.
  11. As a maintainer, I want the removal to also stop the background fetch for this Site, so that the saving is a request not made rather than a response discarded.
  12. As a maintainer, I want the documented behaviour for this Site to say the client does not scan it, so that the next reader of the adapter does not restore the scan as a missing feature.
  13. As a maintainer, I want a test that fails if a lightnovelworld scan is reintroduced, so that this decision is pinned rather than remembered.
  14. As an agent picking up later work on this Site, I want the reason for the removal recorded on the ticket, so that "the client should check too" is not proposed again from scratch.

Implementation Decisions

  • Delete latestChapterFromAnchors from the lightnovelworld adapter in the novel userscript. Do not stub it to return null: absence is what the fetch guard keys off, and a stub would keep the background fetch happening.
  • Both call paths go dead for this Site, deliberately. The background fetch path and the on-page DOM path (Reader standing on a bookmarked Series page) both route through the shared latest-chapter computation. The on-page path is instant and costs no fetch, so losing it is a real cost: on a non-divergent novel the Series page no longer records the newest chapter on sight, and Latest Chapter is up to one poll cooldown stale. Accepted, because on a divergent novel that path already yields nothing, and because keeping it means porting #87's truncation and its fail-closed skip into a second codebase to defend a window of at most one hour. The DOM path reads a document that contains the wpdiscuz thread, so keeping it unscoped without truncation would hand a stranger's comment the shared row.
  • The shared latest-chapter computation tolerates an adapter with no scanner and returns nothing, rather than throwing. It is called from two places and only one of them can be guarded cheaply.
  • The background refresh skips a Site whose adapter has no scanner before selecting due Series, so no Series page is fetched and no freshness timestamp is recorded for this Site. A skipped Site must not consume the per-navigation batch slot.
  • Nothing else about the lightnovelworld adapter changes. Page detection, the pointer-read Series identity from #89, the Chapter Slug carried on the page object, and the stale-row repair are all untouched.
  • novelfull's scanner, the shared max-chapter helper and the anchor extractors stay as they are. The helper is still used by novelfull and by the manga script.
  • The manga userscript is not touched.
  • No backend change. #87 already delivered the authority path.
  • Documentation: the Site's entry in the userscript agent notes records that the client performs no latest-chapter scan for this Site and why (the Poll's cooldown dominates the client throttle, and the comment thread is a public write surface). The Site's live-URL shape notes stay accurate.

Testing Decisions

A good test here asserts what the script computes for a Site, not which functions exist on an adapter object. Asserting that a property is undefined pins the implementation shape and passes for the wrong reasons; asserting that no anchor set produces a Latest Chapter for this Site pins the behaviour a Reader and a shared row actually depend on.

  • Seam: the existing node --test suite for the novel script, driving the script's module exports — the same seam that already covers the adapter scans, the max-chapter helper and the stale-row repair. No new seam, no new file, no test framework.
  • One new export: the shared latest-chapter computation, so the suite can address "what does this script compute for site X" at the highest existing point rather than through an adapter method that is being removed. This is the single seam the change needs.
  • Tests to add:
    • For lightnovelworld, the computation yields nothing for a realistic Series-page anchor set — current-slug chapter anchors, a second Chapter Slug's anchors, and an anchor of the shape a wpdiscuz comment would carry. This is the regression pin: it fails the moment a scan is reintroduced, scoped or not.
    • For novelfull, the computation still returns the maximum and still ignores another Series' chapters, proving the removal is Site-local.
  • Tests to remove: the existing lightnovelworld scan test, which asserts the behaviour being deleted. Its novelfull sibling and the empty-input case for the max-chapter helper stay.
  • Prior art: the adapter scan tests in the novel suite, and the kagane case in the manga suite, which already covers a Site whose anchors are deliberately not scanned.
  • Commands: node --check userscript/novel-bookmark.user.js and node --test userscript/test/novel-logic.test.js. No backend run is needed; the manga suite should stay green untouched.

Out of Scope

  • Porting #87's comment-thread truncation to the client. That is the alternative this spec rejects; if the on-page path is later judged worth keeping, it returns as its own ticket with the truncation and its fail-closed skip.
  • Any change to the Poll, its cooldowns, its fetcher routing or its lightnovelworld scan.
  • The client throttle and batch constants. They are shared across Sites and this change removes one Site from the loop, not the loop.
  • novelfull, Asura, Demonic, comix and kagane.
  • The stale-row repair from #89.
  • Showing the Reader that a Site is Poll-only. Silent is the existing contract for a failed check.

Further Notes

Measured facts behind the decision, so it can be re-argued against numbers rather than intuition:

  • lightnovelworld routes to the plain-TLS fetcher, so its Poll cooldown is the default one hour and it has no browser dependency to degrade — unlike novelfull, whose client scan therefore keeps earning its place.
  • The client check is throttled to one check per Series every four hours, one Series per navigation.
  • Both client paths read a document containing the server-rendered wpdiscuz thread. Today only the stored-slug scoping keeps a comment's anchor out of the maximum, and a computed Latest Chapter is written back to the row every Reader of that Series shares.

Acceptance criteria:

  • No anchor set produces a Latest Chapter for lightnovelworld, including one carrying a comment-shaped anchor to a high-numbered chapter of another novel
  • No Series page is fetched in the background while browsing lightnovelworld, and no freshness timestamp is recorded for its Series
  • Standing on a lightnovelworld Series page writes no Latest Chapter
  • novelfull's scan, throttling and freshness behaviour are unchanged
  • The manga userscript is byte-for-byte unchanged
  • The Site's documented behaviour records that the client does not scan it, and why
  • node --check userscript/novel-bookmark.user.js and node --test userscript/test/novel-logic.test.js pass
> *This was generated by AI during triage.* ## Problem Statement A Reader tracking a lightnovelworld novel whose chapters are published under more than one Chapter Slug sees the userscript's own latest-chapter check contribute nothing. Since #89 the stored `series_id` is the Series slug read from the page pointer, while chapter addresses carry a Chapter Slug; the client scan scopes its anchor match to the stored slug, so on exactly the divergent novels this line of work exists to fix it matches zero anchors and yields no Latest Chapter. The failure is silent and the value is never wrong — the backend Poll is the authority for Latest Chapter and #87 already unscoped it there. What the Reader gets is staleness between polls, on one Site, on the subset of novels that were already the hardest case. ## Solution Remove the client-side latest-chapter scan for lightnovelworld rather than porting #87's fix to it. The Poll refreshes a lightnovelworld Series roughly four times more often than the client ever can (poll cooldown one hour, plain-TLS fetcher, no per-navigation gating; client throttle four hours, one Series per navigation), so the client scan on this Site is an optimisation that optimises nothing while carrying a shared-row poisoning risk that fixing it would require defending. novelfull's client scan is untouched, as are all four manga Sites. ## User Stories 1. As a Reader tracking a divergent lightnovelworld novel, I want the Latest Chapter I see to come from the Poll, so that the number is right whether or not the Series publishes under one Chapter Slug or several. 2. As a Reader tracking any lightnovelworld novel, I want a New Chapter to appear within a poll cooldown, so that the ember means what it says without the userscript needing to look for itself. 3. As a Reader browsing lightnovelworld, I want the userscript to stop fetching a megabyte-scale Series page in the background, so that navigating the Site is not slowed by work the backend already did an hour ago. 4. As a Reader whose novels are shared with other Readers on the same row, I want no stranger's comment on a Series page to be able to set the Latest Chapter, so that a public comment box cannot move a value my library depends on. 5. As a Reader standing on a lightnovelworld Series page, I want the page never to write a Latest Chapter derived from the comment thread below the chapter list, so that the one path that reads the live DOM is as safe as the Poll. 6. As a Reader tracking a novelfull novel, I want the background check to keep working exactly as it does today, so that removing one Site's scan does not cost me another Site's freshness. 7. As a Reader tracking manga, I want the manga userscript untouched, so that a novel-side change cannot regress Asura, Demonic, comix or kagane. 8. As a Reader on lightnovelworld, I want the Bookmark button, progress recording, dwell timer and stale-row repair to behave exactly as before, so that only the latest-chapter check changes. 9. As a Reader who is offline, I want the removal to change nothing about queueing and replay, so that a parked write still lands when I come back. 10. As a maintainer, I want the comment-thread truncation invariant to live in exactly one place, so that a Site redesign is a one-file fix rather than two copies drifting apart. 11. As a maintainer, I want the removal to also stop the background fetch for this Site, so that the saving is a request not made rather than a response discarded. 12. As a maintainer, I want the documented behaviour for this Site to say the client does not scan it, so that the next reader of the adapter does not restore the scan as a missing feature. 13. As a maintainer, I want a test that fails if a lightnovelworld scan is reintroduced, so that this decision is pinned rather than remembered. 14. As an agent picking up later work on this Site, I want the reason for the removal recorded on the ticket, so that "the client should check too" is not proposed again from scratch. ## Implementation Decisions - **Delete `latestChapterFromAnchors` from the lightnovelworld adapter** in the novel userscript. Do not stub it to return null: absence is what the fetch guard keys off, and a stub would keep the background fetch happening. - **Both call paths go dead for this Site, deliberately.** The background fetch path and the on-page DOM path (Reader standing on a bookmarked Series page) both route through the shared latest-chapter computation. The on-page path is instant and costs no fetch, so losing it is a real cost: on a non-divergent novel the Series page no longer records the newest chapter on sight, and Latest Chapter is up to one poll cooldown stale. Accepted, because on a divergent novel that path already yields nothing, and because keeping it means porting #87's truncation and its fail-closed skip into a second codebase to defend a window of at most one hour. The DOM path reads a document that contains the wpdiscuz thread, so keeping it unscoped without truncation would hand a stranger's comment the shared row. - **The shared latest-chapter computation tolerates an adapter with no scanner** and returns nothing, rather than throwing. It is called from two places and only one of them can be guarded cheaply. - **The background refresh skips a Site whose adapter has no scanner** before selecting due Series, so no Series page is fetched and no freshness timestamp is recorded for this Site. A skipped Site must not consume the per-navigation batch slot. - **Nothing else about the lightnovelworld adapter changes.** Page detection, the pointer-read Series identity from #89, the Chapter Slug carried on the page object, and the stale-row repair are all untouched. - **novelfull's scanner, the shared max-chapter helper and the anchor extractors stay as they are.** The helper is still used by novelfull and by the manga script. - **The manga userscript is not touched.** - **No backend change.** #87 already delivered the authority path. - **Documentation:** the Site's entry in the userscript agent notes records that the client performs no latest-chapter scan for this Site and why (the Poll's cooldown dominates the client throttle, and the comment thread is a public write surface). The Site's live-URL shape notes stay accurate. ## Testing Decisions A good test here asserts what the script computes for a Site, not which functions exist on an adapter object. Asserting that a property is `undefined` pins the implementation shape and passes for the wrong reasons; asserting that no anchor set produces a Latest Chapter for this Site pins the behaviour a Reader and a shared row actually depend on. - **Seam:** the existing node `--test` suite for the novel script, driving the script's module exports — the same seam that already covers the adapter scans, the max-chapter helper and the stale-row repair. No new seam, no new file, no test framework. - **One new export:** the shared latest-chapter computation, so the suite can address "what does this script compute for site X" at the highest existing point rather than through an adapter method that is being removed. This is the single seam the change needs. - **Tests to add:** - For lightnovelworld, the computation yields nothing for a realistic Series-page anchor set — current-slug chapter anchors, a second Chapter Slug's anchors, and an anchor of the shape a wpdiscuz comment would carry. This is the regression pin: it fails the moment a scan is reintroduced, scoped or not. - For novelfull, the computation still returns the maximum and still ignores another Series' chapters, proving the removal is Site-local. - **Tests to remove:** the existing lightnovelworld scan test, which asserts the behaviour being deleted. Its novelfull sibling and the empty-input case for the max-chapter helper stay. - **Prior art:** the adapter scan tests in the novel suite, and the kagane case in the manga suite, which already covers a Site whose anchors are deliberately not scanned. - **Commands:** `node --check userscript/novel-bookmark.user.js` and `node --test userscript/test/novel-logic.test.js`. No backend run is needed; the manga suite should stay green untouched. ## Out of Scope - Porting #87's comment-thread truncation to the client. That is the alternative this spec rejects; if the on-page path is later judged worth keeping, it returns as its own ticket with the truncation and its fail-closed skip. - Any change to the Poll, its cooldowns, its fetcher routing or its lightnovelworld scan. - The client throttle and batch constants. They are shared across Sites and this change removes one Site from the loop, not the loop. - novelfull, Asura, Demonic, comix and kagane. - The stale-row repair from #89. - Showing the Reader that a Site is Poll-only. Silent is the existing contract for a failed check. ## Further Notes Measured facts behind the decision, so it can be re-argued against numbers rather than intuition: - lightnovelworld routes to the plain-TLS fetcher, so its Poll cooldown is the default one hour and it has no browser dependency to degrade — unlike novelfull, whose client scan therefore keeps earning its place. - The client check is throttled to one check per Series every four hours, one Series per navigation. - Both client paths read a document containing the server-rendered wpdiscuz thread. Today only the stored-slug scoping keeps a comment's anchor out of the maximum, and a computed Latest Chapter is written back to the row every Reader of that Series shares. Acceptance criteria: - [ ] No anchor set produces a Latest Chapter for lightnovelworld, including one carrying a comment-shaped anchor to a high-numbered chapter of another novel - [ ] No Series page is fetched in the background while browsing lightnovelworld, and no freshness timestamp is recorded for its Series - [ ] Standing on a lightnovelworld Series page writes no Latest Chapter - [ ] novelfull's scan, throttling and freshness behaviour are unchanged - [ ] The manga userscript is byte-for-byte unchanged - [ ] The Site's documented behaviour records that the client does not scan it, and why - [ ] `node --check userscript/novel-bookmark.user.js` and `node --test userscript/test/novel-logic.test.js` pass
sulthan added ready-for-agent and removed needs-triage labels 2026-08-11 20:09:19 +07:00
Author
Owner

This was generated by AI during triage.

Implemented per the spec on this ticket — branch ticket/91-client-lnw-scan-removed, PR #93.

  • lightnovelworld.latestChapterFromAnchors deleted, not stubbed: absence is what the background-fetch guard keys off, so no Series page is fetched and no freshness timestamp is recorded for this Site.
  • computeLatestChapter yields null for an adapter without a scanner and is exported as the test seam; both call paths (on-page + background) go dead for this Site.
  • novelfull's scanner, the shared max-chapter helper and all four manga Sites untouched; the manga userscript is byte-for-byte unchanged.
  • userscript/AGENTS.md records that the client performs no latest-chapter scan for this Site, and why (the Poll's one-hour cooldown dominates the client's four-hour throttle; the wpdiscuz thread is a public write surface).

30/30 novel tests pass — the old lnw scan test was replaced with a computeLatestChapter pin asserting no anchor set yields a Latest Chapter for lightnovelworld, comment-shaped anchors included, plus a novelfull positive proving the removal is Site-local. Manga suite 35/35, node --check clean. All seven acceptance criteria met.

Known gap, accepted in the spec: standing on a lightnovelworld Series page no longer records the newest chapter on sight, so Latest Chapter is up to one poll cooldown (1h) stale on non-divergent novels; on divergent novels that path already yielded nothing.

> *This was generated by AI during triage.* Implemented per the spec on this ticket — branch ticket/91-client-lnw-scan-removed, PR #93. - `lightnovelworld.latestChapterFromAnchors` deleted, not stubbed: absence is what the background-fetch guard keys off, so no Series page is fetched and no freshness timestamp is recorded for this Site. - `computeLatestChapter` yields null for an adapter without a scanner and is exported as the test seam; both call paths (on-page + background) go dead for this Site. - novelfull's scanner, the shared max-chapter helper and all four manga Sites untouched; the manga userscript is byte-for-byte unchanged. - `userscript/AGENTS.md` records that the client performs no latest-chapter scan for this Site, and why (the Poll's one-hour cooldown dominates the client's four-hour throttle; the wpdiscuz thread is a public write surface). 30/30 novel tests pass — the old lnw scan test was replaced with a `computeLatestChapter` pin asserting no anchor set yields a Latest Chapter for lightnovelworld, comment-shaped anchors included, plus a novelfull positive proving the removal is Site-local. Manga suite 35/35, `node --check` clean. All seven acceptance criteria met. Known gap, accepted in the spec: standing on a lightnovelworld Series page no longer records the newest chapter on sight, so Latest Chapter is up to one poll cooldown (1h) stale on non-divergent novels; on divergent novels that path already yielded nothing.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sulthan/mangaBookmark#91