21 KiB
Machine-readable completion markers on the six Sites' Series pages
Research note for Gitea issue #129 (part of #114, "dashboard suggests potentially finished Series"): for each of the six Sites, does the Series page publish a completion marker a Poll could read — an explicit status field, not a heuristic — and how trustworthy is it? The dashboard side (storage, presentation, containment) is issue #130's job, not this note's.
All facts fetched live on 2026-08-19. Sources are the live pages (URLs
below, fetched 2026-08-19) and the in-tree adapter code
(backend/internal/latest/…, branch main @ f5d3fe5). Every claim carries a
URL or a file:line. Interpretation rather than observation is marked
[INFERENCE]. Raw fetches archived in /tmp/cm/ (throwaway).
Fetch method per Site: plain TLS (curl) where the site serves real HTML;
real Chrome via the repo's CDP sidecar pattern (Playwright) where a Cloudflare
challenge blocks plain fetch — comix.to and kagane.to always, novelfull.com
time-varying (today it 403'd plain fetch and cleared instantly in Chrome).
1. Summary verdict table
| Site | Status signal | Fixed vocabulary | Hiatus/dropped ≠ completed | Where the marker lives | Adapter cost |
|---|---|---|---|---|---|
| asurascans.com | Yes — visible block + JSON in page state | {ongoing, completed, hiatus, dropped} (fixed, verified via browse filter) |
Yes — four distinct values | Series page Status block; JSON in astro-island props; badge on listing cards; no chapter-list END row | Low–Medium |
| demonicscans.org | Yes — info-block label/value pair | exactly {Ongoing, Completed} |
No — the site cannot express hiatus/dropped | Series page info block only; search filter; no badges on listings, no END rows | Medium |
| comix.to | Yes — status key in initial-data JSON (same blob the adapter parses) + header badge |
{releasing, finished, on_hiatus} observed; 5 values in browse filter incl. discontinued, not yet released |
Yes — on_hiatus distinct; discontinued a separate value |
Series page header badge; initial-data JSON; browse-card chips |
Low |
| kagane.to | Yes — two explicit fields in the API JSON the adapter already fetches | {Ongoing, Completed, Hiatus} observed on both fields (99-series sample); no Dropped seen |
Yes for hiatus (upload_status), dropped unobservable |
Series API JSON (publication_status, upload_status); two badges on the page; no status filter in search |
Low |
| novelfull.com | Yes — info-panel status link into /status/<value> taxonomy |
exactly {Ongoing, Completed} (OnGoing/Ongoing alias); Hiatus/Dropped 404 |
No — not expressible | Series page info panel; /status/<value> taxonomy pages; no chapter-list END row |
Medium |
| lightnovelworld.net | Yes — visible class-named span and JSON-LD creativeWorkStatus |
{Ongoing, Completed, Hiatus} (search filter radios; no dropped) |
Yes — PotentialActionStatus (hiatus) ≠ CompletedActionStatus |
Series page .sertostat span; <script type="application/ld+json"> in the head; no badges on listings, no END rows |
Low–Medium |
Bottom line: all six Sites publish an explicit, machine-readable completion marker on the Series page. None requires inferring completion from absence of updates. Four of six (asura, comix, kagane, lnw) express hiatus as a distinct value; demonic and novelfull have a binary ongoing/completed vocabulary, which is safe for the Poll (a stalled-but-unlabelled series reads "ongoing") but cannot surface dropped/hiatus. Only kagane separates work status from translation/upload status — the exact distinction the false-positive question cares about. Details and quotes per Site in §2; the false-positive verdict in §3.
2. Per-Site findings
2.1 asurascans.com — four-value status, JSON in page state
Fetch: plain TLS, 200, no challenge (all pages below, 2026-08-19). Series URLs
are /comics/<slug> where <slug> ends in a build hash the site appends
(adapter strips it: asuraBuildHash, sites.go:73).
Series page — visible Status block (ongoing sample,
https://asurascans.com/comics/chronicles-of-the-demon-faction-f886a8af):
<div class="text-xs text-white/50 mb-1">Status</div>
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-[#A78BFA]"></span>
<span class="text-base font-bold text-[#A78BFA] capitalize"> ongoing </span>
</div>
The completed sample (https://asurascans.com/comics/solo-leveling) renders the
same block with the value completed. The label is a plain-text span — the
value must be extracted from the text node, not a class.
Same value in machine-readable JSON. The series page state (serialized into
the astro-island props, HTML-escaped in the served document) carries
"status":[0,"completed"] (Solo Leveling) /
[0,"ongoing"] (ongoing samples) — one key inside the same props
object the adapter already scans for the chapter list.
Vocabulary: fixed, four values. Homepage cards and browse pages embed one
status per card; the browse filter enumerates the set:
https://asurascans.com/browse?status=completed, …?status=hiatus,
…?status=dropped each return 20-card lists whose blobs read
"status":[0,"completed"], [0,"hiatus"],
[0,"dropped"]. Homepage: 72 ongoing + 4 hiatus card blobs.
Semantics of dropped — scanlation-editorial, not work-ended
[INFERENCE, evidence below]. https://asurascans.com/comics/demon-king-b60d532c
is labelled dropped and its chapter links stop at …/chapter/14; the work
continues elsewhere. So the Poll must treat only completed as
potentially-finished; dropped/hiatus mean the site stopped releasing.
Other marker locations. Listing cards carry a badge:
<span class="text-xs font-medium px-2 py-1 rounded capitalize bg-[#913FE2]/20 text-[#A78BFA]"> ongoing </span>
for ongoing, and … bg-[#86EFAC]/20 text-[#86EFAC]"> completed </span> for
completed — the color does not discriminate (completed, hiatus and dropped
all share the green #86EFAC badge; ongoing is purple #913FE2), so a badge
reader must read the text. No structured chapter-list END row: Solo
Leveling's final chapter is titled Side Story 21 { THE END } — the marker is
prose inside a chapter title, unusable as a signal.
Adapter touchpoints. asuraLatestChapter (sites.go:158) scopes chapter
anchors by asuraSlugRe (sites.go:67); cover via ogImageCover
(sites.go:334, wired sites.go:450). The status key lives in the same
escaped props blob the chapter-list scan already walks — a new regex on the
same body, or a parse of the visible Status block.
2.2 demonicscans.org — binary vocabulary, nothing else expressible
Fetch: plain TLS, 200 (2026-08-19). Series URLs /manga/<title>.
Series page — info block (ongoing sample,
https://demonicscans.org/manga/Catastrophic-Necromancer):
<div class="flex flex-row">
<li style="width:150px;color:#b2b2b2;">Status</li>
<li>Ongoing</li>
</div>
Completed sample (https://demonicscans.org/manga/Solo-Leveling): same markup,
<li>Completed</li>. The row sits inside the same info-block <div> as
Author / Rating / Last Update — a sibling <li> pair per field.
Vocabulary: exactly {Ongoing, Completed}. The advanced-search filter
(https://demonicscans.org/advanced.php) proves the set — the status <select>
has two options:
<select class="form-control" name="status">
<option value="all">All</option>
<option value="ongoing">Ongoing</option>
<option value="completed">Completed</option>
</select>
There is no hiatus/dropped value anywhere on the site ([INFERENCE]: no such
string in the filter or any series page sampled). A scanlation that stalls
silently keeps reading Ongoing; the site cannot label a series
dropped/on-hiatus.
Other marker locations. None: homepage and /lastupdates.php show no
status text or badges on their cards (2026-08-19). No chapter-list END rows
(the last-chapter anchor is a plain chaptered.php link).
Adapter touchpoints. demonicLatestChapter (sites.go:177) scans
chaptered.php?manga=<id>&chapter=<n> anchors via demonicChapterRe
(sites.go:77), unscoped; cover ogImageCover (sites.go:334, wired
sites.go:457). A status reader adds a selector over the info-block <li>
pair; the block is sloppy HTML (bare <li> inside <div>s) but stable across
the two sampled themes/versions.
2.3 comix.to — status key in the exact JSON the adapter already parses
Fetch: Cloudflare JS challenge; cleared through a real Chrome (~12 s), then the
adapter's own payload shape was fetched in-tab (2026-08-19). The payload is
server-rendered HTML carrying <script id="initial-data">…</script> JSON
(comixInitialDataRe, sites.go:248); the Series detail entry
["manga","detail","<slug>"] contains "status" and the chapter list.
Observed values (three sampled series, all 2026-08-19):
| Series | URL | status |
Header badge |
|---|---|---|---|
| Dungeons & Crayons (ongoing) | https://comix.to/title/n8we-dungeons-and-crayons |
"releasing" |
RELEASING |
| Countach (completed) | https://comix.to/title/q77m-countach |
"finished" |
FINISHED |
| How to Survive as the Daughter… (hiatus) | https://comix.to/title/d0n08-how-to-survive-as-the-daughter-of-the-emperor-who-killed-me |
"on_hiatus" |
ON_HIATUS |
The visible header badge (hiatus sample):
<span class="mpage__badge mpage__badge--status mpage__badge--status-on_hiatus">ON_HIATUS</span>
The class encodes the value (--status-on_hiatus), the text is the raw
uppercase snake_case label.
Vocabulary: five fixed values from the browse advanced filter ("RELEASE
STATUS"): Releasing, Finished, On hiatus, Discontinued,
Not yet released. The filter URL encodes snake_case:
https://comix.to/browse?statuses=on_hiatus (plural statuses; a plain
?status=finished probe did not filter the list — titles of other statuses
remained). discontinued and not yet released were not observed on series
pages but are site-selectable filter values [INFERENCE: they map to discontinued/not_yet_released keys].
Hiatus/dropped ≠ completed: yes — on_hiatus is a distinct JSON value and
discontinued is a separate filter option.
Other marker locations. Browse cards render status chips (FINISHED / RELEASING seen); the header badge above; the browse filter itself. No chapter-list END row (chapter entries are per-chapter rows).
Adapter touchpoints. comixLatestChapter (sites.go:185) already parses
this exact ["manga","detail","<slug>"] entry for the chapter URL — status
is a sibling key of the entry, so the read cost is near-zero. Cover
comixCoverEntry (sites.go:344). Browser-backed read comixRead
(browser.go:154), Done excludes the interstitial (sites.go:471).
2.4 kagane.to — two explicit status fields in the API JSON the adapter already fetches
Fetch: Cloudflare JS challenge; cleared through a real Chrome (12–32 s, flaky;
the challenge occasionally re-fires). The adapter's payload shape was fetched
in-tab: the Series page itself requests
https://kagane.to/api/v2/series/<uuid> (kaganeSeriesRe, browser.go:25) and
the poller consumes that JSON (kaganeRead, browser.go:128). Series URLs are
/series/<uuid>, e.g. https://kagane.to/series/019f84bc-9ba0-7ed9-86f5-8b905ec7c28b
(Infinite Decryption: The Strongest Level 0).
The API JSON carries two explicit status fields plus year bounds:
"publication_status": "Ongoing",
"upload_status": "Ongoing",
"start_year": 2025,
"end_year": null,
publication_status— the work's status (Ongoing / Completed / Hiatus).upload_status— the site's own release status.
They diverge — proven. "'Cause Calypso Can"
(https://kagane.to/series/019c2034-f431-73ac-9350-11bc498f8c40):
"publication_status": "Ongoing" but "upload_status": "Hiatus" — the work is
ongoing, the site's release is paused. This is precisely the
translation-stopped-vs-work-ended distinction the dashboard's false-positive
question needs, and kagane is the only Site that separates the two.
Vocabulary. Across a 99-series sample (search page 1, in-tab re-POST of the
page's own POST /api/v2/search/series?page=0&size=99 request with body
{"content_rating":["Safe","Suggestive"]} — the page's own request returned
38 results, a direct re-POST 403'd even with Referer), both fields take only
{Ongoing, Completed, Hiatus}. No Dropped value observed on either field
in the sample; a dropped release presumably stays Hiatus or Ongoing
[INFERENCE]. The search page exposes no status filter (filters: Format,
Genres, Tags, People, Sources, Content Rating, Language, Presets).
Page renders two badges (value in text only, class carries no value), the hiatus one yellow:
<span class="inline-flex items-center rounded-full border px-2.5 py-0.5 … bg-yellow-500/10 text-yellow-600 dark:text-yellow-400">Hiatus</span>
Adapter touchpoints. kaganeLatestChapter (sites.go:197) scans
"chapter_no":"<n>" in the API body (kaganeChapterRe, sites.go:98);
cover kaganeCoverEntry (sites.go:349). publication_status/upload_status
are top-level keys of the same body — read cost near-zero.
2.5 novelfull.com — binary status, taxonomy-linked
Fetch: time-varying challenge. Plain TLS today returned HTTP 403 with a
"Just a moment…" interstitial (~5.7 KB); the same URL cleared instantly through
a real Chrome, and the cleared DOM is the adapter's payload shape (novelfullRead,
browser.go:141; Done excludes the interstitial, sites.go:501).
Series page — info panel (completed sample,
https://novelfull.com/reverend-insanity.html):
<div><h3>Status:</h3><a href="/status/Completed">Completed</a></div>
Ongoing sample (https://novelfull.com/a-cunning-pervert-in-the-cultivation-world.html):
<a href="/status/Ongoing">Ongoing</a> in the same block.
Vocabulary: exactly {Ongoing, Completed}, proven by the taxonomy pages:
https://novelfull.com/status/Ongoing and https://novelfull.com/status/OnGoing
both return 200 with identical listings (the two spellings alias; the page title
normalizes to "Status: Ongoing"), while https://novelfull.com/status/Hiatus and
https://novelfull.com/status/Dropped return 404. No hiatus/dropped state
exists on the site.
Hiatus/dropped ≠ completed: no — not expressible; a stalled novel keeps
reading Ongoing indefinitely. Safe direction for the Poll (nothing falsely
"completed"), but the site cannot surface stalled releases.
Other marker locations. The /status/<value> taxonomy listing pages; the
series-page info panel only. Homepage latest-updates cards carry no status
(2026-08-19). No chapter-list END row (chapter rows are plain anchors;
checked reverend-insanity.html's cleared DOM).
Adapter touchpoints. novelfullLatestChapter (sites.go:204) scopes
chapter anchors by novelfullSlugRe (sites.go:103); cover novelfullCoverEntry
(sites.go:339). The status <a href="/status/…"> in the info panel is a new,
independent selector on the same body.
2.6 lightnovelworld.net — class-named span + schema.org JSON-LD
Fetch: plain TLS, 200 (2026-08-19). Series URLs /novel/<slug>/.
Visible marker — .sertostat span, class = value (completed sample,
https://lightnovelworld.net/novel/a-will-eternal/):
<div class="sertostat">
<span class="Completed">Completed</span>
</div>
Ongoing sample (https://lightnovelworld.net/novel/all-jobs-and-classes-i-just-wanted-one-skill-not-them-all/):
<span class="Ongoing">Ongoing</span>; hiatus sample
(https://lightnovelworld.net/novel/rebuild-world-wn/): <span class="Hiatus">Hiatus</span>.
Machine-readable marker — JSON-LD in the head. The same pages carry a
<script type="application/ld+json"> block (line 469 of the a-will-eternal
fetch) with a schema.org Book entry whose creativeWorkStatus is the
completion signal, mapped to the ActionStatus vocabulary:
| Sample | creativeWorkStatus |
|---|---|
| A Will Eternal (completed) | "https://schema.org/CompletedActionStatus" |
| All Jobs and Classes… (ongoing) | "https://schema.org/ActiveActionStatus" |
| Rebuild World (hiatus) | "https://schema.org/PotentialActionStatus" |
Vocabulary: {Ongoing, Completed, Hiatus}. The homepage search filter
enumerates it: radio inputs id="anime_status-ongoing",
id="anime_status-completed", id="anime_status-hiatus" — no dropped option.
Hiatus ≠ completed: yes — PotentialActionStatus (hiatus) is a distinct
JSON-LD value from CompletedActionStatus; the Poll can treat only the latter
as potentially-finished.
Other marker locations. The .sertostat span only. Homepage/listing cards
carry no status badges (2026-08-19 — earlier word hits on the homepage were
the filter radios and a series title containing "Dropped"). No chapter-list
END row: the chapter list is epl-num/epl-title rows with no end marker on
the completed A Will Eternal.
Adapter touchpoints. lnwLatestChapter (sites.go:222) scopes chapter
anchors by lnwChapterRe (sites.go:115) and truncates at the
lnwCommentMarker ("wpd-threads", sites.go:124); cover ogImageCover
(sites.go:334, wired sites.go:508). The JSON-LD block is a separate script
in the head — one regex or JSON parse away; the .sertostat span is in the
article body.
3. The false-positive question — cross-site verdict
The dashboard wants to suggest "potentially finished" without false positives (issue #129 context). Per Site, what would a Poll have to believe to mark a Series finished, and can that belief be wrong?
| Site | Poll reads | False-positive exposure |
|---|---|---|
| asurascans.com | status == completed only |
dropped/hiatus are distinct values — reading them as anything but completed is a false negative, not a false positive. completed is the site's own editorial call on a finished work (Solo Leveling). Safe. |
| demonicscans.org | Status == Completed |
Binary vocabulary: Completed is the site's label and hiatus/dropped are unexpressible, so a label never lies about completion — but "Completed" is site-editorial and could mark a translation ended early. Poll a labelled Completed series as a suggestion, not a fact. |
| comix.to | status == finished |
on_hiatus and discontinued are distinct values; only finished triggers. Same editorial caveat. Safe. |
| kagane.to | publication_status == Completed or upload_status == Completed |
The only Site where the two questions are answered separately. Work-finished should read publication_status; upload_status == Completed is the site's release-complete flag and can coexist with publication_status == Ongoing (inverse of the Calypso Can case [INFERENCE]). The Poll should decide which field drives "potentially finished". |
| novelfull.com | Status == Completed |
Binary vocabulary; hiatus/dropped unexpressible (taxonomy 404s). A labelled Completed (e.g. Reverend Insanity, The Legendary Mechanic) is the site's call. No hiatus false-positive path exists. |
| lightnovelworld.net | creativeWorkStatus == CompletedActionStatus |
PotentialActionStatus (hiatus) is distinct; only CompletedActionStatus triggers. Safest single-value reading of the six. |
Cross-site verdict: no Site's status field is owner-written
series.finished_at data (that stays the owner's prerogative, #114/#124
context) — every value here is the site's editorial status. But none of the
six forces the Poll to guess: all publish explicit markers, and only asura
and kagane introduce translation-state vocabulary (dropped / upload_status)
that a naive "status == completed" reader could misinterpret — in both cases
the danger is a false negative (treating a dropped/hiatus series as still
ongoing), not a false positive, provided the Poll reads only the
completed/finished value and treats anything else as not-completed.
4. Adapter touchpoints (for #130's containment decision)
| Site | Marker source | Existing parse this piggybacks on | New parse needed |
|---|---|---|---|
| asura | escaped "status":[0,"…"] in astro-island props, or visible Status block |
asuraLatestChapter walks the same props blob (sites.go:158) |
one regex on the same body |
| demonic | info-block <li> pair |
— (demonicLatestChapter scans chapter anchors only, sites.go:177) |
selector over the info block |
| comix | ["manga","detail",…]."status" in initial-data |
comixLatestChapter parses that exact entry (sites.go:185, sites.go:248) |
sibling key read |
| kagane | publication_status / upload_status top-level keys |
kaganeLatestChapter reads the same API body (sites.go:197) |
sibling key read (×2) |
| novelfull | info-panel <a href="/status/…"> |
— (novelfullLatestChapter scopes chapter anchors, sites.go:204) |
selector over the info panel |
| lightnovelworld | head JSON-LD creativeWorkStatus, or .sertostat span |
— (lnwLatestChapter truncates at wpd-threads, sites.go:222) |
one JSON-LD regex / parse |
Challenge status for a future Poll: asura, demonic, lnw are plain-fetch; comix and kagane require the browser sidecar on every poll (Cloudflare challenge present today, 2026-08-19); novelfull requires the sidecar whenever its challenge is up (it was up today, 2026-08-19).