lightnovelworld Series identity is read from the chapter page (#80) #92

Merged
sulthan merged 17 commits from feat/lnw-series-identity into main 2026-08-11 18:21:51 +07:00
Owner

Implements spec #80 / ADR-0008 — Gitea issues #86, #87, #88, #89, #90, all closed.

A Reader bookmarks a novel on lightnovelworld and it never shows a New Chapter, because the Series identity was derived from the chapter address instead of read from the page. One Series can publish under several Chapter Slugs, so the derived key points at a slug that 404s.

  • #89 — the userscript's lnw adapter stops deriving seriesUrl/seriesId from the path. It reads the page's own pointer (a[aria-label='All Chapter']), falling back to the microdata breadcrumb's second crumb, and carries chapterSlug on the page object, stored nowhere.
  • #87 — the Poll's lnw chapter scan is unscoped (no stored-slug pattern can cover a Series' whole list) and truncated at the wpd-threads comment thread, the one region a visitor can write to. Marker absent means skip and log with the body length, never scan whole. Corrects the maxBodyBytes headroom comment to the measured 3.5x.
  • #86 — the scan fixture is now text trimmed from a real, wholly-fetched Series page instead of a hand-written cross-series anchor that no live page carries.
  • #90 — stale stored rows repair themselves on the next chapter visit: a pure transform over cache, queue and last-checked map, silent to the Reader, with progress, favourite and lifecycle bucket preserved when two rows merge.
  • #88 — an env-gated live canary (SMOKE_LNW_SERIES_URL) proving the marker still occurs exactly once and still follows the last chapter anchor, asserted against the production symbols themselves.

Verified on the merged branch: go test ./... green, gofmt -l internal/latest/ silent, both userscripts node --check clean, 35/35 + 29/29 logic tests. Live canary green (marker once at byte 612,182 of 651,795). #90 verified on device with Playwright.

Open follow-up: #91 — the userscript's client-side latest-chapter scan is still scoped to the derived slug.

Implements spec #80 / ADR-0008 — Gitea issues #86, #87, #88, #89, #90, all closed. A Reader bookmarks a novel on lightnovelworld and it never shows a New Chapter, because the Series identity was derived from the chapter address instead of read from the page. One Series can publish under several Chapter Slugs, so the derived key points at a slug that 404s. - **#89** — the userscript's lnw adapter stops deriving `seriesUrl`/`seriesId` from the path. It reads the page's own pointer (`a[aria-label='All Chapter']`), falling back to the microdata breadcrumb's second crumb, and carries `chapterSlug` on the page object, stored nowhere. - **#87** — the Poll's lnw chapter scan is unscoped (no stored-slug pattern can cover a Series' whole list) and truncated at the `wpd-threads` comment thread, the one region a visitor can write to. Marker absent means skip and log with the body length, never scan whole. Corrects the `maxBodyBytes` headroom comment to the measured 3.5x. - **#86** — the scan fixture is now text trimmed from a real, wholly-fetched Series page instead of a hand-written cross-series anchor that no live page carries. - **#90** — stale stored rows repair themselves on the next chapter visit: a pure transform over cache, queue and last-checked map, silent to the Reader, with progress, favourite and lifecycle bucket preserved when two rows merge. - **#88** — an env-gated live canary (`SMOKE_LNW_SERIES_URL`) proving the marker still occurs exactly once and still follows the last chapter anchor, asserted against the production symbols themselves. Verified on the merged branch: `go test ./...` green, `gofmt -l internal/latest/` silent, both userscripts `node --check` clean, 35/35 + 29/29 logic tests. Live canary green (marker once at byte 612,182 of 651,795). #90 verified on device with Playwright. Open follow-up: **#91** — the userscript's client-side latest-chapter scan is still scoped to the derived slug.
sulthan added 15 commits 2026-08-11 13:38:13 +07:00
The lightnovelworld chapter branch no longer derives the Series address by
string-manipulating the chapter path: on ~7% of novels the Chapter Slug
diverges from the Series slug and the derived address 404s on every Poll.
The identity now comes from the page's own a[aria-label='All Chapter']
pointer (breadcrumb's second crumb as fallback), validated as a
lightnovelworld.net /novel/<slug>/ address; no pointer resolves to
type: other. The Chapter Slug rides on the detected page object only
(chapterSlug; null on a series page) and is written to no store, for the
stale-row repair in #90.

Harness: document.querySelector now answers attribute selectors with an
element exposing getAttribute (attrEls table, cleared by reset()).
AGENTS.md records that the Series address is discovered, not derived.
The a-will-eternal fixture carried a hand-written cross-series anchor
(overgeared-chapter-9999) that no real series page produces. Replace it
with text trimmed from a real, wholly-fetched
all-jobs-and-classes-i-just-wanted-one-skill-not-them-all page (2026-08-11,
~307 KB decoded): chapter anchors under both Chapter Slugs (1-99 at
...-not, 100-423 at ...-not-them-all, page order, boundary numbers and max
423), the wpd-threads marker exactly once, and after it a real wpdiscuz
comment block from the-sword-illuminates-the-great-wilderness (the pinned
page serves zero comments; same Site, same theme) carrying the one composed
element - a bare <a href> to a real overgeared chapter (2059, HTTP 200).

Scan expectation moves 1317 -> 423, the no-trailing-slash and challenge
cases move to the new novel, lnwCoverFixture moves to the new novel's real
og:image, and the poller's cover-fill case follows. sites.go and fetch.go
are byte-for-byte unchanged.
Review follow-up: the parenthetical about the research note's ~1.18 MB
bound is not locatable from code and would rot. Keep the size disclosure,
drop the cross-reference.
- Share one lnw host regex between matches and the pointer validator so
  the two cannot drift apart.
- Resolve the pointer href against the page address before validating,
  so a relative pointer still yields its Series instead of silently
  resolving to type other.
- Pin pointer/breadcrumb agreement on the divergent page and pin that a
  pointer off-host or not /novel/<slug>/ resolves to other.
A row keyed under the Chapter Slug its address was built from, while the
page pointer names a different Series, is stale. Rewrite its key, identity
and address in the cache, the retry-queue entry (if any) and the per-device
last-checked map together, then sync through the queue-backed path so an
offline repair parks and replays. Pure transform exported for the harness;
wired before render() at both detect() call sites. A row already under the
repaired key merges, keeping the farther-ahead progress, favourite OR'd and
the stronger lifecycle bucket.
The Poll scoped the lightnovelworld scan to the slug derived from the
stored address, so a Series publishing chapters under more than one
Chapter Slug froze its Latest Chapter. The branch now matches any
chapter-shaped address on the host (lnwChapterRe) and scans only the
body before the wpdiscuz thread marker (wpd-threads); a body without
the marker is skipped and logged with its length, never scanned whole.
The size-cap comment now states the measured ~3.5x headroom.
Cross-reference lnwChapterRe from the function doc instead of restating
its rationale, drop the redundant '; skipping' from the fail-closed log
to match the package's bare verb:detail form, and reword the truncation
comment so the logged body length is not described as a code-made
distinction.
sulthan added 2 commits 2026-08-11 18:00:33 +07:00
sulthan merged commit e7e22a12a5 into main 2026-08-11 18:21:51 +07:00
Sign in to join this conversation.