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 `