Files
mangaBookmark/CONTEXT.md
T
sulthan 30c57bd39c Define Cover and record hosting its bytes (#47) (#64)
Defines **Cover** in the glossary and records ADR-0007, the decision behind #47's fix.

## Why these two files, and why now

`CONTEXT.md` named Cover inside the **Series** entry — "facts true regardless of who is reading — title, cover, Latest Chapter" — but never said *what* one is. That gap is the bug. Nothing in the model distinguished "an address on a Site" from "an image a Reader's browser can display", so both clients were left to work it out independently, and one of them got it wrong. kagane serves covers with `cross-origin-resource-policy: same-origin`, the web UI rewrote them to a proxy in its templates, the JSON API did not, and the panel rendered a broken-image glyph. The new entry closes the ambiguity: *an address no client can load is not a Cover, it is a missing one.*

ADR-0007 records what follows from that — the backend fetches, stores and serves every Site's cover bytes — plus the alternatives that were rejected and, more importantly, the two places this deliberately departs from existing precedent:

- **Destination-class control instead of a host allowlist.** `fetchableSeriesURL` sets the allowlist precedent for `series_url`, and covers do not follow it. Cover hosts are CDNs that move independently of their Site — demonicscans serves its covers from `readermc.org` — so an allowlist would stop producing Covers the day a Site switched CDN, and that failure would look exactly like #47. The resolve-then-classify step is what actually stops the SSRF.
- **A public cover route where the kagane proxy is session-gated.** An `<img>` cannot send a bearer token, and it cannot be given one either: the panel's shadow root is `mode: "open"`, so the host page's JavaScript can read any `src` the script sets.

Both are security-adjacent departures, which is precisely why they are written down rather than left in a commit message.

## Scope

Documentation only — no code, no schema, no behaviour. The implementation is #56–#63.

## Why this should merge promptly rather than sit

All eight implementation tickets cite `docs/adr/0007-backend-hosts-cover-bytes.md` as the authority for decisions they must not relitigate, and they are written in the vocabulary this glossary entry defines. An agent picking up #56 reads both from `main`. Until this lands they get a 404 and either invent a rationale or stall — so this PR gates the tickets, not the other way round.

Related: #47 (bug), #55 (spec), #54 (deferred admin refetch).
Reviewed-on: #64
Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
2026-08-09 23:19:37 +07:00

3.3 KiB

Bookmark Manager

Read-progress tracker for serialised fiction. A reader browses third-party manga and novel sites; userscripts capture where they got to and sync it to a self-hosted backend, so progress survives across sites and devices.

Language

Series: One ongoing work — a manga or a novel — as published by a Site. Identified by its stable slug on that Site, never by its title. A Series exists once and is shared by every Reader who bookmarks it; it owns the facts that are true regardless of who is reading — title, cover, Latest Chapter. A Reader cannot change them; they describe the Series, not anyone's relationship to it. Avoid: manga, title, book, comic

Site: One third-party source a Series is published on. A Series on two Sites is two Series. Avoid: source, host, provider, domain

Cover: The image that stands for a Series wherever it is listed. A fact about the Series like its title — one Cover per Series, shared by every Reader, never per-Reader. Defined by what a Reader's browser can display, not by where the Site keeps the picture: an address no client can load is not a Cover, it is a missing one. Avoid: thumbnail, poster, image URL, artwork

Reader: A person with their own Progress. Exactly one per set of credentials, so there is no separate "account" concept to model — the credential belongs to the Reader. Avoid: user, account, member, subscriber

Bookmark: One Reader's tracked relationship with one Series, holding only what differs between Readers: Progress, Favourite, Lifecycle bucket. Facts about the Series itself belong to the Series, not here. Avoid: entry, item, record, subscription

Library: One of the two halves of the collection — manga or novel — selected by a Bookmark's kind. The web UI and the userscripts each address exactly one Library at a time. Not a per-person concept: "everything one person has bookmarked" is a different idea and must not be called a Library. Avoid: section, tab, category

Progress: The furthest chapter a reader has actually read in a Series. Only a change in Progress is real activity, so only Progress reorders the list. Avoid: position, bookmark (the noun is taken), last read

Latest Chapter: The newest chapter a Site has published for a Series, discovered without the reader present. Distinct from Progress in every way that matters: it is a fact about the Site, not about the reader, and it must never reorder the list. Avoid: newest, current chapter, update

Poll: The backend's own check of a Site for a Series's Latest Chapter, made without the Reader present. Performed once per Series no matter how many Readers bookmarked it — a Poll is work done on behalf of the Series, never on behalf of a Reader. Avoid: scrape, refresh, check, sync

New Chapter: The state where Latest Chapter is ahead of Progress. The single condition the ember accent is permitted to signal. Avoid: unread, update available

Lifecycle bucket: Which of three mutually exclusive states a Bookmark sits in — reading, archived, or finished. A Bookmark is in exactly one. Orthogonal to being a favourite. Avoid: state, status (as a domain word), list

Favourite: A reader's manual pin on a Bookmark. Orthogonal to the Lifecycle bucket, and never a reason to reorder the list. Avoid: starred, pinned, priority