Covers render in the userscript panel, from a public route (#60) #69
Reference in New Issue
Block a user
Delete Branch "feat/60-public-cover-route"
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?
Closes #60.
Spec: #55. Originating bug: #47. Architecture:
docs/adr/0007-backend-hosts-cover-bytes.md. Neither #47 nor #55 is closed from here.What this branch does
The panel now renders Covers from the deployment's own origin, and both userscripts stop having an opinion about where a Cover lives.
The public route was already in place.
GET /covers/{address}landed with #59 (92eba07) and is registered on the bare mux, outsidehttpmw.Authand outside the web UI's Discord session —backend/main.go:210-214, handlerbackend/internal/api/handlers.go:142-158. It reads no cookie and no header, answers404for an address that was never stored (and for a row whose file has gone missing — recorded-but-gone is not-found, never a fabricated body), refuses anything that is not^[0-9a-f]{64}$before the value becomes a path, and setsCache-Control: public, max-age=604800, immutable. Those four properties are asserted bybackend/cover_test.go:231-278. This branch re-verified them rather than re-implementing them; the only backend line it touches is a comment.Both userscripts lose cover scraping entirely. Every adapter's
cover:field is gone, along with the two helpers that fed them: the manga script'scoverFromPage()(theimg[alt]DOM scan comix needed, because comix publishes noog:image) and the novel script'smetaName()plus the now-callerless module-levelmeta(). Nothing underuserscript/readsog:image,meta[name=image], orimg[alt]any more.Nothing sends a cover either.
delete body.coversits inapiPut—manga-bookmark.user.js:486,novel-bookmark.user.js:275— which is the single chokepoint every write passes through (pushBookmark, the retry-queue flush,toggleFavorite,toggleArchive). It operates on theObject.assigncopy, so the in-memory row keeps the cover it renders with. This matters beyond tidiness: a Reader upgrading from an older copy haslocalStoragerows carrying third-party scraped URLs, and without the strip those would ride back up on the next write. The handler discards the field regardless (handlers.go:53-59) — it is permanently inert, not pending removal.Failed loads get the designed empty state, not the broken-image glyph.
onerror: (e) => e.target.replaceWith(el("div", { class: "cover ph" }))on the cover<img>in both card renderers (manga:1380-1390,novel:1134-1144). The replacement is byte-identical to the existing no-cover branch on the very next line, so it picks up the.cover.phstyling already in the panel CSS — no new tokens, no new rule.el()routes anyon*prop throughaddEventListener, so this is a listener, not an inline attribute string, and the swap is acreateElement+ DOM call with no markup parsing anywhere near it. This is the half of #47 that was visible on kagane.The deleted scraping's tests went with it: the two comix cover cases, the
pageImagesandnamedMetasfixtures, theimg[alt]andmeta[name=...]stub branches, the now-deadquerySelectorAllstub member, and every staleog:imagefixture andp.coverassertion across both suites. The export lists needed no change and that was checked, not assumed —coverFromPageandmetaNamewere module-private onorigin/mainand no cover symbol ever appeared inmodule.exports.Docs that described the deleted behaviour were corrected in the same breath, because leaving them would instruct the next agent to put the scraping back:
userscript/AGENTS.md(adapter contract + the per-site notes for comix, kagane and novelfull), the README's adapter reference, and the userscript testing skill's stub table.Verification
go test -count=1 ./...— green across all nine packages (backend29.8s,latest,store,session,token,userscript,web).node --checkclean on both userscripts;node --teston both logic suites — 46 tests, 46 pass.gofmt -lclean;go build ./...clean.onerrorswap is DOM behaviour and deliberately has no coverage in the Node harness — that harness stubs a browser precisely so it never needs a DOM, and #60 says not to invent coverage for it. It was instead exercised for real: theel()helper and the exact render expression were loaded into a headless Chromium with a deliberately unloadablesrc, and the resulting DOM was<div class="cover ph"></div>. Ad hoc, not committed.Reviewed
Both
/code-reviewaxes ran againstcc0fa92. Spec found no missed requirement and no scope creep; standards found the diff clean on the four areas it scrutinised (thedelete body.coverplacement, theonerrorhandler's DOM safety, comment quality, dead-code removal). Their combined findings — the deadquerySelectorAllstub, the stale README and skill text, and the handler comment whose premise this change invalidates — are fixed in8b58019.Out of scope, deliberately
The kagane-specific cover proxy still exists and still carries its session gate (#63 deletes it). The poll's blank-Cover fill (#61) and browser-backed Sites joining the pipeline (#62) are untouched.
Review follow-ups. The README's adapter reference and the userscript testing skill still described a scrape that no longer exists, and the manga test stub kept a querySelectorAll whose only caller was the deleted coverFromPage. The handler comment's premise ("every installed userscript still sends one") stops being true the moment a Reader reinstalls, so it now says older copies may.