novelfull cover never appears in the userscript list after bookmarking #78
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
Bookmarking novelfull Star Odyssey from the userscript: the list row draws the
.cover.phplaceholder, not the cover, and it stays that way.What is actually happening
Two separate things, both real. The first explains "not instantly" and is certain; the
second explains "never" and depends on what the VPS log says.
1. The userscript can never see a cover acquired after its own PUT
Covers are acquired asynchronously (ADR-0007):
Store.OnSeriesCreated->Acquirer.Acquirespawns a goroutine (
backend/internal/latest/acquire.go:66-89) after the PUT has alreadycommitted and returned. So the PUT response carries
cover: ""by construction — the bytesdo not exist yet.
The userscript adopts that response verbatim (
pushBookmark->upsertLocal(saved),novel-bookmark.user.js:554-557) and persists it underCACHE_KEY. The row is now cachedwith an empty cover.
The only thing that ever replaces a cached row with the server's is
refresh(), and it runsin exactly two places:
novel-bookmark.user.js:1045— panel opennovel-bookmark.user.js:1338— script init (page load)There is no timer, no
visibilitychange, nofocuslistener. The user bookmarks from theopen panel, so the refresh that would have picked the cover up already happened, seconds
before the cover existed. For the rest of that page's life the row shows a placeholder.
Same gap in
manga-bookmark.user.js— same structure, same two refresh sites.It does heal on the next page load or panel open, so if the cover is genuinely absent after
reopening the panel, it is #2 below, not this.
2. novelfull's series page 403s the plain-TLS fetcher
Measured today from this machine, through the backend's own
TLSFetcher(Chrome_133profile), not curl:
That is the Cloudflare interstitial.
Acquirer.acquirebails atacquire.go:118-121withstatus 403, before bothlatestChapterFromandcoverFrom, sothe Series gets neither a chapter nor a cover — and
MarkLatestCheckednever runs either.The poll's
fillBlankCoverheals a blank Cover only from a series page it managed tofetch, so it needs the same fetch to start succeeding.
fetcherForprefers the browser for novelfull, so this only bites whenBROWSER_WS_URLis unset or the sidecar is asleep/unreachable (ADR-0005/0006) — which is a supported state,
and per AGENTS.md the challenge is a live time-varying fact, not a fixed property.
Discriminator: grep the VPS log for
acquire "novelfull:star-odyssey". Astatus 403line means #2; nothing at all means acquisition succeeded and #1 is thewhole story.
Not the cause — ruled out
<meta name="image" content="https://novelfull.com/uploads/webp/novel/star-odyssey-8035594265.webp">,which is exactly what
coverFrom->metaContent(body, "name", "image")reads(
sites.go:247-248)..webpanswers a plain, UA-less GET with200 image/webp 41706— no challenge on the uploads path, soTLSCoverFetcheris fine.main.go:338-348passesBrowserFetch,BrowserCoverFetchandCoversto theAcquirer.
overlayPendingreturns the server list untouched when the queue isempty (
novel-bookmark.user.js:508-523).Fix direction
For #1, the lazy version: when an adopted PUT response has
cover === ""for a row theclient just created, schedule one delayed
refresh()(acquisition is bounded atacquireTimeout = 45s, and covers usually land in a few seconds — ~10s then, once, noloop). Both userscripts. Anything push-shaped is far more machinery than this earns.
For #2, nothing to change in code — it is the documented degrade path. Worth confirming the
sidecar is reachable from the VPS before treating it as a defect.
Scope
userscript/novel-bookmark.user.js,userscript/manga-bookmark.user.js(syncUpsert/pushBookmark,refresh)userscript/test/novel-logic.test.js,userscript/test/logic.test.js