Remove the client latest-chapter scan for lightnovelworld (#91) (#93)

Follows the spec published on #91: remove the client-side latest-chapter scan for lightnovelworld rather than porting #87's truncation into a second codebase.

- lightnovelworld.latestChapterFromAnchors deleted, not stubbed: absence is what the background-fetch guard keys off.
- computeLatestChapter tolerates an adapter with no scanner (yields null) and is exported as the test seam.
- backgroundRefreshLatest skips a scanner-less Site before the due filter: no Series page fetched, no freshness timestamp recorded, no batch slot consumed. The on-page path (maybeCaptureLatestOnSeriesPage) routes through the same null-tolerant computation.
- novelfull's scanner, the shared max-chapter helper and all four manga Sites untouched.
- userscript/AGENTS.md records the Poll-only contract for this Site and why.

All seven acceptance criteria from the spec met. node --check clean; novel suite 30/30 (the regression pin fails if a lnw scan is reintroduced, scoped or not); manga suite 35/35, manga userscript byte-for-byte unchanged.

Two-axis code review: no hard standard violations, spec-clean; one follow-up commit matching the sibling adapter guard from the manga script.

Reviewed-on: #93
Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
This commit was merged in pull request #93.
This commit is contained in:
2026-08-11 20:58:18 +07:00
committed by sulthan
parent e7e22a12a5
commit 672c16ffbf
8 changed files with 11560 additions and 11724 deletions
+3 -6
View File
@@ -15,8 +15,10 @@
"13": "Web UI Handlers",
"14": "Go Error Handling",
"15": "CDP Browser Client",
"16": "Store",
"17": "Go Code Style Guide",
"18": "Agent Skills",
"19": "Open",
"20": "I/O Performance Patterns",
"21": "CPU Optimization",
"22": "Caching Patterns",
@@ -35,6 +37,7 @@
"35": "Web UI Filter Logic",
"36": "Userscript Test Harness",
"37": "Product & Security Context",
"38": "novel-logic.test.js",
"39": "UI Critique 2026-07-26A",
"40": "UI Critique 2026-07-26B",
"41": "DB Cutover Runbook",
@@ -269,11 +272,5 @@
"270": "Redeploy Runbook",
"271": "Rollback Strategy",
"272": "Bearer-token API client (apiGet/apiPut/apiDelete)",
"273": "Userscript AGENTS guidance",
"274": "manga-bookmark.user.js (single-IIFE manga script)",
"275": "novel-bookmark.user.js (novel script, bmgr:novel:)",
"276": "Offline retry queue (bmgr:manga:queue markers)",
"277": "Shadow DOM UI isolation from site CSS",
"278": "Site adapters (URL-regex detect, og:title)",
"279": "Userscript CLAUDE guidance"
}
+52 -32
View File
@@ -1,16 +1,16 @@
# Graph Report - mangaBookmark (2026-08-11)
## Corpus Check
- 108 files · ~265,471 words
- 108 files · ~265,771 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 1644 nodes · 3206 edges · 277 communities (62 shown, 215 thin omitted)
- Extraction: 90% EXTRACTED · 10% INFERRED · 0% AMBIGUOUS · INFERRED: 312 edges (avg confidence: 0.77)
- 1637 nodes · 3195 edges · 274 communities (66 shown, 208 thin omitted)
- Extraction: 90% EXTRACTED · 10% INFERRED · 0% AMBIGUOUS · INFERRED: 305 edges (avg confidence: 0.77)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `27dda601`
- Built from commit: `fb65c78e`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@@ -31,8 +31,10 @@
- [[_COMMUNITY_Web UI Handlers|Web UI Handlers]]
- [[_COMMUNITY_Go Error Handling|Go Error Handling]]
- [[_COMMUNITY_CDP Browser Client|CDP Browser Client]]
- [[_COMMUNITY_Store|Store]]
- [[_COMMUNITY_Go Code Style Guide|Go Code Style Guide]]
- [[_COMMUNITY_Agent Skills|Agent Skills]]
- [[_COMMUNITY_Open|Open]]
- [[_COMMUNITY_IO Performance Patterns|I/O Performance Patterns]]
- [[_COMMUNITY_CPU Optimization|CPU Optimization]]
- [[_COMMUNITY_Caching Patterns|Caching Patterns]]
@@ -51,6 +53,7 @@
- [[_COMMUNITY_Web UI Filter Logic|Web UI Filter Logic]]
- [[_COMMUNITY_Userscript Test Harness|Userscript Test Harness]]
- [[_COMMUNITY_Product & Security Context|Product & Security Context]]
- [[_COMMUNITY_novel-logic.test.js|novel-logic.test.js]]
- [[_COMMUNITY_UI Critique 2026-07-26A|UI Critique 2026-07-26A]]
- [[_COMMUNITY_UI Critique 2026-07-26B|UI Critique 2026-07-26B]]
- [[_COMMUNITY_DB Cutover Runbook|DB Cutover Runbook]]
@@ -284,12 +287,6 @@
- [[_COMMUNITY_Redeploy Runbook|Redeploy Runbook]]
- [[_COMMUNITY_Rollback Strategy|Rollback Strategy]]
- [[_COMMUNITY_Bearer-token API client (apiGetapiPutapiDelete)|Bearer-token API client (apiGet/apiPut/apiDelete)]]
- [[_COMMUNITY_Userscript AGENTS guidance|Userscript AGENTS guidance]]
- [[_COMMUNITY_manga-bookmark.user.js (single-IIFE manga script)|manga-bookmark.user.js (single-IIFE manga script)]]
- [[_COMMUNITY_novel-bookmark.user.js (novel script, bmgrnovel)|novel-bookmark.user.js (novel script, bmgr:novel:)]]
- [[_COMMUNITY_Offline retry queue (bmgrmangaqueue markers)|Offline retry queue (bmgr:manga:queue markers)]]
- [[_COMMUNITY_Shadow DOM UI isolation from site CSS|Shadow DOM UI isolation from site CSS]]
- [[_COMMUNITY_Site adapters (URL-regex detect, ogtitle)|Site adapters (URL-regex detect, og:title)]]
- [[_COMMUNITY_Userscript CLAUDE guidance|Userscript CLAUDE guidance]]
## God Nodes (most connected - your core abstractions)
@@ -297,7 +294,7 @@
2. `newWebTestServer()` - 49 edges
3. `newTestStore()` - 42 edges
4. `newTestStore()` - 41 edges
5. `e()` - 33 edges
5. `e()` - 29 edges
6. `Handler` - 29 edges
7. `ne()` - 28 edges
8. `De()` - 28 edges
@@ -309,12 +306,12 @@
chrome/docker-compose.yml → backend/AGENTS.md
- `el()` --indirect_call--> `c()` [INFERRED]
userscript/manga-bookmark.user.js → backend/internal/web/static/htmx.min.js
- `el()` --indirect_call--> `c()` [INFERRED]
userscript/novel-bookmark.user.js → backend/internal/web/static/htmx.min.js
- `latestChapterFromAnchors()` --indirect_call--> `re()` [INFERRED]
userscript/manga-bookmark.user.js → backend/internal/web/static/htmx.min.js
- `el()` --indirect_call--> `k()` [INFERRED]
userscript/manga-bookmark.user.js → backend/internal/web/static/htmx.min.js
- `makeDraggable()` --indirect_call--> `e()` [INFERRED]
userscript/manga-bookmark.user.js → backend/internal/web/static/htmx.min.js
## Import Cycles
- None detected.
@@ -330,7 +327,7 @@
- **Headless browser infrastructure (sidecar, on-demand, home deployment)** — docs_adr_0005_on_demand_browser_headless_shell, docs_adr_0005_on_demand_browser_cdp, docs_adr_0005_on_demand_browser_on_demand_start, docs_adr_0006_browser_on_the_home_machine_home_machine_rationale [INFERRED 0.85]
- **lightnovelworld series-identity investigation and fix** — docs_research_lightnovelworld_chapter_vs_series_slug_issue_77, docs_research_lightnovelworld_chapter_vs_series_slug_unscoped_regex, docs_adr_0008_series_identity_is_discovered_not_derived_discovered_identity [INFERRED 0.85]
## Communities (277 total, 215 thin omitted)
## Communities (274 total, 208 thin omitted)
### Community 0 - "HTMX Library Internals"
Cohesion: 0.08
@@ -345,8 +342,8 @@ Cohesion: 0.06
Nodes (76): adapterFor(), anchorsFromDocument(), anchorsFromHTML(), apiDelete(), apiGet(), apiPut(), applyFabPos(), applyLatestChapterIfChanged() (+68 more)
### Community 3 - "Novel Userscript Adapters"
Cohesion: 0.06
Nodes (79): adapterFor(), anchorsFromDocument(), anchorsFromHTML(), apiDelete(), apiGet(), apiPut(), applyFabPos(), applyLatestChapterIfChanged() (+71 more)
Cohesion: 0.08
Nodes (71): adapterFor(), anchorsFromDocument(), anchorsFromHTML(), apiDelete(), apiGet(), apiPut(), applyFabPos(), applyLatestChapterIfChanged() (+63 more)
### Community 4 - "Series Acquisition Tests"
Cohesion: 0.10
@@ -373,8 +370,8 @@ Cohesion: 0.05
Nodes (39): Test Helpers, Test Timeout, Basic Handler Test, HTTP Handler Testing, Query Parameters and Headers, Docker Compose Fixture, Integration Testing, SQL Schema Fixture (+31 more)
### Community 11 - "Store Tests"
Cohesion: 0.05
Nodes (75): T, TestCreateAndGetSession(), TestDeleteSessionIsPerReader(), TestDeleteSessionRevokes(), TestExpiredSessionIsGone(), TestGetSessionUnknownID(), applyMigration(), CoverContentType() (+67 more)
Cohesion: 0.14
Nodes (44): scanSeries(), Store, T, newTestStore(), readLatestCheckedAt(), readSeries(), secondReader(), seedForCheck() (+36 more)
### Community 12 - "Bookmarks API Handler"
Cohesion: 0.08
@@ -392,16 +389,24 @@ Nodes (33): Creating Errors, Custom Error Types, Custom types that wrap other er
Cohesion: 0.08
Nodes (29): Action, awaitPromise(), browserConnectionLost(), classifyBrowserError(), Context, Mutex, isInterstitial(), jsString() (+21 more)
### Community 16 - "Store"
Cohesion: 0.09
Nodes (7): CoverContentType(), coverRelativePath(), coverSourceAddress(), displayChapter(), Store, TestDisplayChapter(), Bookmark
### Community 17 - "Go Code Style Guide"
Cohesion: 0.08
Nodes (23): Code Style Details, Extract Complex Conditions, Value vs Pointer Arguments, Code Organization Within Files, Complex Conditions & Init Scope, Composite Literals, Control Flow, Cross-References (+15 more)
### Community 19 - "Open"
Cohesion: 0.19
Nodes (18): applyMigration(), migrate(), Open(), refreshOwnerToken(), seedOwner(), TestCoverIsContentAddressedOnFilesystem(), TestCoverPersistsAcrossReopen(), TestMigration0002BackfillsExistingBookmarks() (+10 more)
### Community 20 - "I/O Performance Patterns"
Cohesion: 0.11
Nodes (18): Avoid io.ReadAll for large payloads, Batch Operations, Buffered I/O, Cgo Overhead, Channel: batch processing from a stream, Concurrent Multi-Stage Pipelines, Connection pooling, Database: batch inserts over row-by-row (+10 more)
### Community 21 - "CPU Optimization"
Cohesion: 0.13
Cohesion: 0.12
Nodes (15): Cache Locality, Contiguous 2D allocation, CPU Optimization, False Sharing, Function Inlining, Handling CPU-specific instruction sets, Instruction-Level Parallelism, Monotonic Time (+7 more)
### Community 22 - "Caching Patterns"
@@ -425,8 +430,8 @@ Cohesion: 0.14
Nodes (13): Common Skill Categories, Find Skills, How to Help Users Find Skills, Step 1: Understand What They Need, Step 2: Check the Leaderboard First, Step 3: Search for Skills, Step 4: Verify Quality Before Recommending, Step 5: Present Options to the User (+5 more)
### Community 28 - "Allocation Patterns"
Cohesion: 0.14
Nodes (14): Allocation Patterns, Backing Array Leaks, Direct indexing vs append, Eliminate redundant map lookups, Interface boxing, Map never shrinks, Map size hints, Memory Optimization (+6 more)
Cohesion: 0.17
Nodes (12): Backing Array Leaks, Map never shrinks, Map of pointers for large, frequently updated structs, Memory Layout, Memory Optimization, Pointer receivers for large structs, Slice reslicing retains the entire backing array, String and Byte Optimization (+4 more)
### Community 29 - "Observability & Alerting"
Cohesion: 0.22
@@ -452,6 +457,17 @@ Nodes (5): closeCardPanels(), setActiveTab(), toggleChapterForm(), toggleConfirm
Cohesion: 0.17
Nodes (11): Accessibility & Inclusion, Brand Commitments, Capabilities and Constraints, Evidence on Hand, Operating Context, Platform, Positioning, Product (+3 more)
### Community 38 - "novel-logic.test.js"
Cohesion: 0.13
Nodes (8): assert, attrEls, elements, metaTags, {
novelfull,
lightnovelworld,
computeLatestChapter,
kindOf,
maxChapter,
repairLnwStaleRow,
}, reset(), store, test
### Community 39 - "UI Critique 2026-07-26A"
Cohesion: 0.29
Nodes (6): Design Health Score, Design Specificity Verdict, Minor Observations, Persona Red Flags, Priority Issues, Questions to Consider
@@ -461,8 +477,8 @@ Cohesion: 0.29
Nodes (6): Design Health Score, Design Specificity Verdict, Minor Observations, Persona Red Flags, Priority Issues, Questions to Consider
### Community 42 - "Memory Layout Patterns"
Cohesion: 0.40
Nodes (5): Map of pointers for large, frequently updated structs, Memory Layout, Pointer receivers for large structs, Struct field alignment, Zero-size field at end of struct
Cohesion: 0.48
Nodes (6): T, TestCreateAndGetSession(), TestDeleteSessionIsPerReader(), TestDeleteSessionRevokes(), TestExpiredSessionIsGone(), TestGetSessionUnknownID()
### Community 45 - "Go Perf Alert Rules"
Cohesion: 0.50
@@ -473,7 +489,7 @@ Cohesion: 0.07
Nodes (27): 1. Summary answer table, 2. The two reference pages, 3. Chapter page → series URL: every in-page pointer, in priority order, 4.1 Sample method, 4.2 Divergence results, 4.3 Is there a derivable rule? **No.**, 4.4 The split case — a slug can change *mid-series*, 4. The reverse direction, and how common divergence is (+19 more)
### Community 47 - "Go Perf Skill Docs"
Cohesion: 0.22
Cohesion: 0.28
Nodes (5): Continuous Profiling, Production Observability for Performance, Pyroscope pull mode (via Grafana Alloy), Pyroscope push mode, Real-Time Visualization (Development)
### Community 48 - "Login Page Art"
@@ -573,28 +589,32 @@ Cohesion: 0.50
Nodes (3): ADR-0005: On-demand browser sidecar, Constraints, Decision
### Community 125 - "AGENTS.md"
Cohesion: 0.29
Nodes (7): Allocation Patterns, Direct indexing vs append, Eliminate redundant map lookups, Interface boxing, Map size hints, Reuse slices via append(s[:0], ...), Sentinel errors vs fmt.Errorf
### Community 272 - "Bearer-token API client (apiGet/apiPut/apiDelete)"
Cohesion: 0.50
Nodes (3): Live URL shapes (verified 2026-07-26, may drift — re-check against live pages before trust), Second script: `novel-bookmark.user.js`, Userscript structure (single IIFE, `manga-bookmark.user.js`)
## Knowledge Gaps
- **545 isolated node(s):** `bookmarkmanager/backend`, `ctxKey`, `loginView`, `ctxKey`, `test` (+540 more)
- **540 isolated node(s):** `test`, `assert`, `store`, `metaTags`, `elements` (+535 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **215 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **208 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `New()` connect `Series Acquisition Tests` to `Bookmarks API Tests`, `Cover & Acquire Internals`, `Session Middleware`, `Store Tests`, `Web UI Handlers`?**
- **Why does `Open()` connect `Open` to `Cover Fetch Test Helpers`, `Series Acquisition Tests`, `Bookmarks API Tests`, `Store Tests`, `Store`, `PG Test Infrastructure`?**
_High betweenness centrality (0.041) - this node is a cross-community bridge._
- **Why does `Open()` connect `Store Tests` to `Cover Fetch Test Helpers`, `Series Acquisition Tests`, `Bookmarks API Tests`, `PG Test Infrastructure`?**
_High betweenness centrality (0.035) - this node is a cross-community bridge._
- **Why does `New()` connect `Series Acquisition Tests` to `Bookmarks API Tests`, `Cover & Acquire Internals`, `Session Middleware`, `Web UI Handlers`, `Open`?**
_High betweenness centrality (0.031) - this node is a cross-community bridge._
- **Why does `newRouter()` connect `Bookmarks API Tests` to `Cover Fetch Test Helpers`, `Bookmarks API Handler`, `Series Acquisition Tests`?**
_High betweenness centrality (0.022) - this node is a cross-community bridge._
_High betweenness centrality (0.030) - this node is a cross-community bridge._
- **Are the 47 inferred relationships involving `testConfig()` (e.g. with `TestListRendersAcquiredCover()` and `TestPublicCoverNeverEchoesNonImage()`) actually correct?**
_`testConfig()` has 47 INFERRED edges - model-reasoned connections that need verification._
- **Are the 8 inferred relationships involving `newWebTestServer()` (e.g. with `TestListRendersAcquiredCover()` and `TestPublicCoverRejectsUnknownAddress()`) actually correct?**
_`newWebTestServer()` has 8 INFERRED edges - model-reasoned connections that need verification._
- **Are the 6 inferred relationships involving `newTestStore()` (e.g. with `TestCreateAndGetSession()` and `TestDeleteSessionIsPerReader()`) actually correct?**
_`newTestStore()` has 6 INFERRED edges - model-reasoned connections that need verification._
- **What connects `bookmarkmanager/backend`, `ctxKey`, `loginView` to the rest of the system?**
_589 weakly-connected nodes found - possible documentation gaps or missing edges._
- **What connects `test`, `assert`, `store` to the rest of the system?**
_582 weakly-connected nodes found - possible documentation gaps or missing edges._
File diff suppressed because one or more lines are too long
+11449 -11661
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -395,13 +395,13 @@
"semantic_hash": "8008a187690764436540fab47ba0cfcc"
},
"userscript/novel-bookmark.user.js": {
"mtime": 1786446006.925938,
"ast_hash": "64f8d2848294c7aeb8b2e9d43a049fd0",
"mtime": 1786454823.798836,
"ast_hash": "834effb0821f8d6c9f57f6554a5db462",
"semantic_hash": ""
},
"userscript/test/novel-logic.test.js": {
"mtime": 1786446006.925938,
"ast_hash": "be732600e57fe4ad2e585e9c84d6f341",
"mtime": 1786454566.070801,
"ast_hash": "b25a7377af210dd0aff8284501fd0252",
"semantic_hash": ""
},
".opencode/agent/implementer.md": {
@@ -510,13 +510,13 @@
"semantic_hash": "69114d07ed792d6bb1d13758ba5435e1"
},
"userscript/AGENTS.md": {
"mtime": 1786446006.9219902,
"ast_hash": "954caa07b01ab323ab205e29f0bcacd8",
"mtime": 1786454605.6727684,
"ast_hash": "e276ffe9a6e7b55fd3235466a1995c22",
"semantic_hash": ""
},
"userscript/CLAUDE.md": {
"mtime": 1786446006.9219902,
"ast_hash": "954caa07b01ab323ab205e29f0bcacd8",
"mtime": 1786454605.6727684,
"ast_hash": "e276ffe9a6e7b55fd3235466a1995c22",
"semantic_hash": ""
},
"backend/internal/web/static/login-art.png": {
+5
View File
@@ -78,6 +78,11 @@ Guidance for OpenCode (and Claude Code) working under `userscript/`. See root `A
the clean title on a series page and `<Title> Chapter <n>` on a chapter page.
Its series page lists every chapter with an
absolute href, so the backend polls it with the plain TLS client.
The client performs no latest-chapter scan for this Site: the Poll's
one-hour cooldown dominates the client's four-hour throttle, so a scan
would add no freshness, and the page's wpdiscuz thread is a public write
surface a scan would have to truncate at. `computeLatestChapter` yields
null here and `backgroundRefreshLatest` skips the Site before any fetch.
### Second script: `novel-bookmark.user.js`
+12 -10
View File
@@ -218,12 +218,6 @@
}
return { type: "other" };
},
latestChapterFromAnchors(anchors, seriesId) {
return maxChapter(
anchors,
new RegExp("lightnovelworld\\.net/" + escapeRe(seriesId) + "-chapter-([0-9.]+)/")
);
},
};
const ADAPTERS = [novelfull, lightnovelworld];
@@ -342,10 +336,11 @@
// Highest chapter the site lists, or null when the markup yields nothing.
// seriesId is only consulted by adapters whose pages carry other series'
// chapter links; the rest ignore it.
// chapter links. An adapter without a scanner (lightnovelworld — see the
// AGENTS.md entry) yields null, not an error.
function computeLatestChapter(site, anchors, seriesId) {
const a = adapterFor(site);
return a ? a.latestChapterFromAnchors(anchors, seriesId) : null;
return a && a.latestChapterFromAnchors ? a.latestChapterFromAnchors(anchors, seriesId) : null;
}
function currentSite() {
@@ -861,6 +856,14 @@
const site = currentSite();
if (!site) return;
// A Site with no client scanner (lightnovelworld) is refreshed by the Poll
// on a cooldown shorter than the client throttle; fetching its pages here
// would be a megabyte-scale request whose result is discarded. Skipping
// before the due filter records no freshness timestamp and consumes no
// per-navigation batch slot.
const adapter = adapterFor(site);
if (!adapter || !adapter.latestChapterFromAnchors) return;
const checked = loadLastChecked();
const now = Date.now();
const due = state.list
@@ -871,7 +874,6 @@
.slice(0, LATEST_CHECK_BATCH);
if (due.length === 0) return;
const adapter = adapterFor(site);
for (const bm of due) {
// Recorded even when the fetch fails, so a broken series is retried on
// the next throttle window rather than on every single page load.
@@ -1750,7 +1752,7 @@
// Exposes pure logic only — see userscript/test/novel-logic.test.js.
// ============================================================
if (typeof window === "undefined" && typeof module === "object" && module.exports) {
module.exports = { novelfull, lightnovelworld, anchorsFromHTML, statusOf, kindOf, maxChapter, escapeRe, repairLnwStaleRow };
module.exports = { novelfull, lightnovelworld, anchorsFromHTML, statusOf, kindOf, maxChapter, escapeRe, computeLatestChapter, repairLnwStaleRow };
}
// ============================================================
+27 -3
View File
@@ -45,6 +45,7 @@ globalThis.document = {
const {
novelfull,
lightnovelworld,
computeLatestChapter,
kindOf,
maxChapter,
repairLnwStaleRow,
@@ -314,17 +315,40 @@ test("lightnovelworld.detect returns other for non-series paths", () => {
assert.equal(lightnovelworld.detect(loc("https://lightnovelworld.net/az-lists/")).type, "other");
});
test("lightnovelworld.latestChapterFromAnchors takes the max and ignores other series", () => {
const best = lightnovelworld.latestChapterFromAnchors(
test("computeLatestChapter yields nothing for lightnovelworld, comment anchors included", () => {
// A realistic Series-page anchor set: current-slug chapters, a second
// Chapter Slug's chapters, and a wpdiscuz comment pasting a high-numbered
// chapter of another novel (the poisoning vector). The client never scans
// this Site — the Poll's cooldown dominates the client throttle and the
// comment thread is a public write surface — so this must fail the moment
// a scan is reintroduced, scoped or not.
const latest = computeLatestChapter(
"lightnovelworld",
[
{ href: "https://lightnovelworld.net/a-will-eternal-chapter-1/", text: "Chapter 1" },
{ href: "https://lightnovelworld.net/a-will-eternal-chapter-1317/", text: "Chapter 1317" },
{ href: "https://lightnovelworld.net/a-will-eternal-chapter-1298/", text: "Chapter 1298" },
// a divergent novel's second Chapter Slug
{ href: "https://lightnovelworld.net/my-longevity-simulation-chapter-400/", text: "Chapter 400" },
// a wpdiscuz comment anchor
{ href: "https://lightnovelworld.net/overgeared-chapter-9999/", text: "Chapter 9999" },
],
"a-will-eternal"
);
assert.deepEqual(best, { num: 1317, label: "Chapter 1317" });
assert.equal(latest, null);
});
test("computeLatestChapter still scans novelfull and ignores other series", () => {
const best = computeLatestChapter(
"novelfull",
[
{ href: "/reverend-insanity/chapter-2334-fang-yuan.html", text: "Chapter 2334" },
{ href: "/reverend-insanity/chapter-1.html", text: "Chapter 1" },
{ href: "/release-that-witch/chapter-9999.html", text: "Chapter 9999" },
],
"reverend-insanity"
);
assert.deepEqual(best, { num: 2334, label: "Chapter 2334" });
});
test("latestChapterFromAnchors returns null when nothing matches", () => {