Bug: comix.to cover still not fetched for the web and userscript so the web is using the placeholder cover image, kagane.to cover image showed up in web but not propperly shown in userscript #47

Closed
opened 2026-08-09 03:45:40 +07:00 by sulthan · 3 comments
Owner

comix.to verified by me, when i bookmark 'Full-Time Awakening' series in comix, it doesnt show the cover in the web and userscript.
for kagane.to in the web it shows the cover but in the userscript it shows only image icon on the top left corner of the cover.

comix.to verified by me, when i bookmark 'Full-Time Awakening' series in comix, it doesnt show the cover in the web and userscript. for kagane.to in the web it shows the cover but in the userscript it shows only image icon on the top left corner of the cover.
sulthan added the bug label 2026-08-09 03:45:40 +07:00
sulthan added the needs-triage label 2026-08-09 17:20:26 +07:00
Author
Owner

This was generated by AI during triage.

Triage Notes

Category bug, state needs-triage — evaluation in progress. Both reported symptoms reproduce; they are two independent defects that happen to land on the same slot.

What we've established so far:

  • comix, cause 1 — nothing to scrape on a chapter page. Verified live 2026-08-09 on comix.to/title/qqwrm-full-time-awakening. On the series page the adapter's coverFromPage works and returns https://static.comix.to/9c57/i/8/6d/68df89f72ae62@280.jpg. On the chapter page (.../11173862-chapter-154) the document has three images, all with alt="Page N", and still no og:image — so coverFromPage correctly returns "". Bookmarking while reading, the normal flow, therefore stores an empty cover.
  • comix, cause 2 — the blank is permanent. Store.Upsert's series ON CONFLICT DO UPDATE writes only kind and the latest-chapter columns; title/series_url/cover are creation-only by ADR-0003. A later visit to the series page cannot fill the blank, and nothing backfills server-side: the poller's prefetchCover is kagane-bytes-only, and no site parser extracts a cover. So even after cause 1 is fixed, series already recorded stay coverless.
  • kagane — a delivery problem, not a capture one. The cover is stored correctly. The web UI renders Bookmark.CoverURL(), which rewrites a kagane cover to the same-origin proxy /img/kagane/{id}. The userscript panel instead renders b.cover directly — the raw https://kagane.to/api/v2/image/<uuid>/compressed, which kagane serves with cross-origin-resource-policy: same-origin behind its challenge (measured 2026-08-08). That is exactly the broken-image icon reported. GET /bookmarks returns the raw cover, never CoverURL, and the proxy route is session-gated, so the bearer-credential userscript cannot reach it as things stand.

Open decisions (grilling next, not agent-ready):

  1. Whether to relax ADR-0003 to fill-if-empty (COALESCE(NULLIF(series.cover,''), excluded.cover)) so a blank cover can be repaired, versus a narrower backfill path.
  2. Where a comix cover comes from at all, given the chapter page has none: a userscript re-PUT from the series page, or a server-side extract during the poll (comix polls fine over plain TLS).
  3. How the userscript reaches a kagane cover: a bearer-authenticated cover route, or having the bookmarks API return the proxied URL rather than the raw one.
> *This was generated by AI during triage.* ## Triage Notes Category `bug`, state `needs-triage` — evaluation in progress. Both reported symptoms reproduce; they are **two independent defects** that happen to land on the same slot. **What we've established so far:** - **comix, cause 1 — nothing to scrape on a chapter page.** Verified live 2026-08-09 on `comix.to/title/qqwrm-full-time-awakening`. On the *series* page the adapter's `coverFromPage` works and returns `https://static.comix.to/9c57/i/8/6d/68df89f72ae62@280.jpg`. On the *chapter* page (`.../11173862-chapter-154`) the document has **three images, all with `alt="Page N"`**, and still no `og:image` — so `coverFromPage` correctly returns `""`. Bookmarking while reading, the normal flow, therefore stores an empty cover. - **comix, cause 2 — the blank is permanent.** `Store.Upsert`'s series `ON CONFLICT DO UPDATE` writes only `kind` and the latest-chapter columns; `title`/`series_url`/`cover` are creation-only by ADR-0003. A later visit to the series page cannot fill the blank, and nothing backfills server-side: the poller's `prefetchCover` is kagane-bytes-only, and no site parser extracts a cover. So even after cause 1 is fixed, series already recorded stay coverless. - **kagane — a delivery problem, not a capture one.** The cover *is* stored correctly. The web UI renders `Bookmark.CoverURL()`, which rewrites a kagane cover to the same-origin proxy `/img/kagane/{id}`. The userscript panel instead renders `b.cover` directly — the raw `https://kagane.to/api/v2/image/<uuid>/compressed`, which kagane serves with `cross-origin-resource-policy: same-origin` behind its challenge (measured 2026-08-08). That is exactly the broken-image icon reported. `GET /bookmarks` returns the raw `cover`, never `CoverURL`, and the proxy route is session-gated, so the bearer-credential userscript cannot reach it as things stand. **Open decisions (grilling next, not agent-ready):** 1. Whether to relax ADR-0003 to fill-if-empty (`COALESCE(NULLIF(series.cover,''), excluded.cover)`) so a blank cover can be repaired, versus a narrower backfill path. 2. Where a comix cover comes from at all, given the chapter page has none: a userscript re-PUT from the series page, or a server-side extract during the poll (comix polls fine over plain TLS). 3. How the userscript reaches a kagane cover: a bearer-authenticated cover route, or having the bookmarks API return the proxied URL rather than the raw one.
Author
Owner

.
5. Wire becomes an absolute URL from a configured public base; until bytes exist.
6. Both userscripts: cover scraping deleted, falls back to the existing placeholder.
7. s sr.Site != "kagane" prefetch guard generalises to all six Sites, both Libraries.

Only kagane still needs the CDP browser. novelfulls HTML is challenge-gated but its image paths
answer 200 with .

Extractor gotchas: asuras .webp cover URL answers Content-Type: image/jpeg (trust the
header); demonics contains a raw unencoded space (percent-encode before fetching).
EOF
)

. 5. Wire becomes an absolute URL from a configured public base; until bytes exist. 6. Both userscripts: cover scraping deleted, falls back to the existing placeholder. 7. s `sr.Site != "kagane"` prefetch guard generalises to all six Sites, both Libraries. Only kagane still needs the CDP browser. novelfulls HTML is challenge-gated but its image paths answer 200 with . Extractor gotchas: asuras `.webp` cover URL answers `Content-Type: image/jpeg` (trust the header); demonics contains a raw unencoded space (percent-encode before fetching). EOF )
Author
Owner

Design settled (grilling session, 2026-08-09). Decisions recorded in
docs/adr/0007-backend-hosts-cover-bytes.md; Cover added to CONTEXT.md; the deferred
admin refetch is #54.

Root causes (verified live, not inferred)

comix — coverFromPage matches img[alt] === document.title. That element exists on the
series page but not on a chapter page (only alt="Page 1..3"), so bookmarking mid-read
stores cover = "", and Store.Upsert writes Series facts only at creation (ADR-0003), so the
blank is permanent. comix serves no og:image anywhere.

kagane — the stored cover is a raw kagane URL. The web UI survives only because
store.CoverURL() rewrites it to /img/kagane/<id> in the templates; the JSON API returns
the raw URL, and kagane sends cross-origin-resource-policy: same-origin, so the userscript's
<img> cannot load it. No onerror anywhere, so it paints the native broken-image glyph.

Novel side has the same bug: lightnovelworld exposes no cover on chapter pages either, so it
fails exactly like comix. novelfull is fine (its meta[name=image] is present on chapter pages).
The novel panel renders covers through identical code, so both scripts are affected.

The fix

  1. Backend acquires Covers from the same series-page fetch that yields Latest Chapter, run once
    at Series creation instead of waiting for the poll queue.
  2. Backend fetches and stores the bytes for every Site, content-addressed on a filesystem
    volume; the DB holds the path. No client ever renders a third-party URL.
  3. Outbound fetch is gated by destination class — https, resolved IP must be public, re-checked
    per redirect hop, size and content-type capped — not by a host allowlist.
  4. One public cover route (an <img> cannot send a bearer token, and the panel's shadow root is
    mode: "open", so a token in src would be readable by the host page).
  5. Wire cover becomes an absolute URL from a configured public base; "" until bytes exist.
  6. Both userscripts: cover scraping deleted, onerror falls back to the existing placeholder.
  7. poller.go's sr.Site != "kagane" prefetch guard generalises to all six Sites, both Libraries.

Only kagane still needs the CDP browser. novelfull's HTML is challenge-gated but its image paths
answer 200 with access-control-allow-origin: *.

Extractor gotchas: asura's .webp cover URL answers Content-Type: image/jpeg (trust the
header); demonic's og:image contains a raw unencoded space (percent-encode before fetching).

Design settled (grilling session, 2026-08-09). Decisions recorded in docs/adr/0007-backend-hosts-cover-bytes.md; `Cover` added to CONTEXT.md; the deferred admin refetch is #54. ## Root causes (verified live, not inferred) **comix** — `coverFromPage` matches `img[alt] === document.title`. That element exists on the series page but **not on a chapter page** (only `alt="Page 1..3"`), so bookmarking mid-read stores `cover = ""`, and `Store.Upsert` writes Series facts only at creation (ADR-0003), so the blank is permanent. comix serves no `og:image` anywhere. **kagane** — the stored cover is a raw kagane URL. The web UI survives only because `store.CoverURL()` rewrites it to `/img/kagane/<id>` **in the templates**; the JSON API returns the raw URL, and kagane sends `cross-origin-resource-policy: same-origin`, so the userscript's `<img>` cannot load it. No `onerror` anywhere, so it paints the native broken-image glyph. **Novel side has the same bug**: lightnovelworld exposes no cover on chapter pages either, so it fails exactly like comix. novelfull is fine (its `meta[name=image]` is present on chapter pages). The novel panel renders covers through identical code, so both scripts are affected. ## The fix 1. Backend acquires Covers from the same series-page fetch that yields Latest Chapter, run once at Series creation instead of waiting for the poll queue. 2. Backend fetches and stores the **bytes** for every Site, content-addressed on a filesystem volume; the DB holds the path. No client ever renders a third-party URL. 3. Outbound fetch is gated by destination class — https, resolved IP must be public, re-checked per redirect hop, size and content-type capped — not by a host allowlist. 4. One public cover route (an `<img>` cannot send a bearer token, and the panel's shadow root is `mode: "open"`, so a token in `src` would be readable by the host page). 5. Wire `cover` becomes an absolute URL from a configured public base; `""` until bytes exist. 6. Both userscripts: cover scraping deleted, `onerror` falls back to the existing placeholder. 7. `poller.go`'s `sr.Site != "kagane"` prefetch guard generalises to all six Sites, both Libraries. Only kagane still needs the CDP browser. novelfull's HTML is challenge-gated but its image paths answer 200 with `access-control-allow-origin: *`. Extractor gotchas: asura's `.webp` cover URL answers `Content-Type: image/jpeg` (trust the header); demonic's `og:image` contains a raw unencoded space (percent-encode before fetching).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sulthan/mangaBookmark#47