From f1eb7d514c2dfe44894810298e0e7155bad12f20 Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Tue, 11 Aug 2026 09:34:26 +0700 Subject: [PATCH] Record the lightnovelworld series-identity decision (#77) (#81) Docs only. No code, no tests, nothing to run. Implementation is specified in #80. Outcome of a grilling session on 2026-08-11 against #77, backed by live measurement of lightnovelworld over 2026-08-10/11. ## What changed **`docs/adr/0008-series-identity-is-discovered-not-derived.md`** (new) A Series identity is discovered from the Site's own links, never derived from an address. On lightnovelworld the userscript reads the chapter page's `All Chapter` anchor instead of building a `/novel//` address by string manipulation. A Chapter Slug is not an identity and is not stored. The backend's chapter scan drops its per-Series scoping and runs against the body truncated before the visitor comment thread. Evidence in the ADR: 3 of 41 sampled novels serve chapters under a slug that differs from their series slug, divergence runs in both directions, one novel serves chapters under two slugs, and neither slug is computable from the other. The pointer was checked on 8 chapter pages and agreed every time. Three narrower selectors are recorded as rejected, each with the measurement that killed it. Three rejected options are recorded with reasons: correcting the stored address only, which keeps an identity the Site does not guarantee; scoping the scan to a container, which the probe refuted; and a SQL migration, which is impossible because the database holds no source for the correct slug. **`CONTEXT.md`** - **Series** - identity is the canonical slug the Site publishes, never the title and never a Chapter Slug. - **Chapter Slug** - new term. A slug a Site builds its chapter addresses from. Not an identity: one Series may have several, and none is computable from another. - **Latest Chapter** - now the highest-numbered chapter, explicitly not a date and not the Site's own newest-chapter banner. Settles #79. **`docs/research/lightnovelworld-chapter-vs-series-slug.md`** (new, committed with its corrections) The 41-novel survey behind the ADR. Two claims are struck through and corrected in place, with the date and sample size of the probe that refuted each: the `ul.clstyle` container it named is the hidden, empty "Latest Reading" template rather than the chapter list, and its caveat about the comment region understated the risk, because that region is writable by any visitor while the scan takes an unbounded maximum into a Series row shared by every Reader (ADR-0003). ## Review notes Nothing here constrains code that exists today - the ADR describes work not yet written. The part worth disagreeing with, if any of it is wrong, is the fail-closed rule: a missing truncation marker means skip the Series and log, never scan the whole page. Related: #77 (the defect), #80 (the spec), #79 (the numbering anomaly, closed by decision), #71 (the same size cap seen from the cover side). Reviewed-on: https://gitea.violetcrown.my.id/sulthan/mangaBookmark/pulls/81 Co-authored-by: Sulthan Zaki Co-committed-by: Sulthan Zaki --- CONTEXT.md | 26 +- ...ries-identity-is-discovered-not-derived.md | 102 ++++ .../lightnovelworld-chapter-vs-series-slug.md | 572 ++++++++++++++++++ 3 files changed, 692 insertions(+), 8 deletions(-) create mode 100644 docs/adr/0008-series-identity-is-discovered-not-derived.md create mode 100644 docs/research/lightnovelworld-chapter-vs-series-slug.md diff --git a/CONTEXT.md b/CONTEXT.md index 399fcfb..2501a21 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,17 +7,24 @@ so progress survives across sites and devices. ## Language **Series**: -One ongoing work — a manga or a novel — as published by a Site. Identified by its -stable slug on that Site, never by its title. A Series exists once and is shared by -every Reader who bookmarks it; it owns the facts that are true regardless of who is -reading — title, cover, Latest Chapter. A Reader cannot change them; they describe the -Series, not anyone's relationship to it. +One ongoing work — a manga or a novel — as published by a Site. Identified by the canonical +slug the Site itself publishes for it, never by its title and never by a Chapter Slug. A +Series exists once and is shared by every Reader who bookmarks it; it owns the facts that +are true regardless of who is reading — title, cover, Latest Chapter. A Reader cannot +change them; they describe the Series, not anyone's relationship to it. _Avoid_: manga, title, book, comic **Site**: One third-party source a Series is published on. A Series on two Sites is two Series. _Avoid_: source, host, provider, domain +**Chapter Slug**: +A slug a Site builds its chapter addresses from. Not an identity: one Series may have +several, any of them may differ from the slug that identifies the Series, and none is +computable from another. Only the Site's own links say which ones a Series uses, so a +Chapter Slug is always discovered, never derived. +_Avoid_: series slug, url slug, permalink, chapter path + **Cover**: The image that stands for a Series wherever it is listed. A fact about the Series like its title — one Cover per Series, shared by every Reader, never per-Reader. Defined by @@ -49,9 +56,12 @@ is real activity, so only Progress reorders the list. _Avoid_: position, bookmark (the noun is taken), last read **Latest Chapter**: -The newest chapter a Site has published for a Series, discovered without the reader -present. Distinct from Progress in every way that matters: it is a fact about the Site, -not about the reader, and it must never reorder the list. +The highest-numbered chapter a Site has published for a Series, discovered without the +reader present. The number is what ranks it, never a date and never the Site's own +"newest chapter" banner — where a Site disagrees with itself, its list of chapters is +the record and its summary of that list is not. Distinct from Progress in every way +that matters: it is a fact about the Site, not about the reader, and it must never +reorder the list. _Avoid_: newest, current chapter, update **Poll**: diff --git a/docs/adr/0008-series-identity-is-discovered-not-derived.md b/docs/adr/0008-series-identity-is-discovered-not-derived.md new file mode 100644 index 0000000..00246a0 --- /dev/null +++ b/docs/adr/0008-series-identity-is-discovered-not-derived.md @@ -0,0 +1,102 @@ +# ADR-0008: A Series identity is discovered from the Site's links, never derived from an address + +Date: 2026-08-11 +Status: accepted + +## Decision + +On lightnovelworld, a Series is identified by the slug in its `/novel//` +address, and the userscript obtains that address by reading the chapter page's +`a[aria-label="All Chapter"]` anchor. It no longer constructs the address by +string manipulation of the chapter path. When neither that anchor nor the +microdata breadcrumb is present, the page resolves to `type: "other"` and no +Bookmark is offered. + +A Chapter Slug — the slug a chapter address is built from — is not an identity +and is not stored. The backend finds chapters by matching +`lightnovelworld\.net/[a-z0-9-]+-chapter-([0-9.]+)/` against the series page +body **truncated at the first `wpd-threads`**. + +## Why + +Measured on lightnovelworld between 2026-08-10 and 2026-08-11. + +The chapter slug and the series slug are two independent facts. In a 41-novel +sample, 3 diverged (~7%). `/my-longevity-simulation-chapter-1/` returns 200 +while `/novel/my-longevity-simulation/` returns 404 with no redirect, and that +novel's real address is `/novel/immortality-simulator/`. Divergence runs in both +directions: `the-sword-illuminates-the-great-wilderness` is served by chapter +slug `radiant-blade-of-the-wilderness`. Neither slug is computable from the +other, and the Site publishes no alternative-names field, so the mapping exists +only in the chapter page's own markup. + +Storing the Chapter Slug beside the identity does not work, because a Series may +have more than one. `/novel/all-jobs-and-classes-i-just-wanted-one-skill-not-them-all/` +serves chapters 1–99 under `…-one-skill-not` and 100–423 under +`…-one-skill-not-them-all`. Both resolve, and both chapter pages point back at +the same Series. + +Deriving the identity from the chapter path also made one Series produce two +rows: bookmarking from the series page yielded `lightnovelworld:immortality-simulator`, +and from a chapter page `lightnovelworld:my-longevity-simulation`. + +The pointer is reliable. Across 8 chapter pages — chapter 1, chapter 1200, the +latest chapter, both slugs of the split novel, two divergent novels, and a novel +with a number in its title — the `All Chapter` anchor and breadcrumb position 2 +were both present and agreed every time, including on the old-slug pages. Three +narrower selectors were rejected on evidence: `a[href*="/novel/"]` matches the +header nav index first; matching the text "All Chapter" false-matches the novel +titled "…Not Them All Chapter 200"; and the JSON-LD breadcrumb's position 2 is +the chapter, not the Series. + +The scan is truncated because a series page server-renders a wpdiscuz comment +thread below the chapter list, and comment bodies are HTML that can carry an +anchor. Verified on `/novel/the-sword-illuminates-the-great-wilderness/`: +comment `#wpd-comm-358_0` rendered in the initial HTML, corroborated by that +page's comment RSS feed. The scanner takes the maximum chapter number with no +upper bound, and a Series row is shared by every Reader (ADR-0003), so one +comment containing a link to a high-numbered chapter would pin that Series' +Latest Chapter for everyone. `wpd-threads` occurs exactly once per page and +follows every chapter anchor on all 4 series pages measured. `wpdcom` and +`wpdiscuz` are unusable: they occur 111 to 143 times per page, including in +`` before the chapter list. + +## Considered options + +**Correct `series_url` only, leaving the Chapter Slug as the identity.** This +repairs the Poll with no migration, because the Poll reads the stored address +rather than the identity. Rejected: it keeps an identity that the Site does not +guarantee to be stable, and leaves the duplicate-row hazard in place. + +**Scope the match to the chapter-list container.** Rejected on measurement. The +prior research named `ul.clstyle`; on all 4 pages sampled that is the hidden, +empty "Latest Reading" template, and the real list is a classless `