Files
mangabaka-seeder/docs/DECISIONS.md
T

153 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Decisions
Append-only. Never edit or delete an entry — supersede it with a new one that links back.
One entry per decision a future maintainer would otherwise have to reverse-engineer.
No index: `grep -n '^## ' docs/DECISIONS.md` lists every entry (date, title, line number).
## 2026-10-14 — Adopt AGENTS.md as single source of truth
**Status:** accepted
**Context:** Agents had no durable project context and no consistent workflow between sessions.
The design context lived in `HANDOVER.md`, which is now split into this file, the spec and `docs/TODO.md`.
**Decision:** `AGENTS.md` is canonical; `CLAUDE.md`/`GEMINI.md` are pointers. Specs are permanent in
`docs/specs/`. Plans live in `docs/plans/` only while work is in progress and are removed before the
merge request. Both override the superpowers default paths.
**Alternatives:** Per-tool rule files (drift, synchronisation debt); no rules file (agents re-explore
the repo every session — measurably slower and more tokens).
**Consequences:** Rule changes happen in one file; pointer files must stay thin. Plans are working
artefacts: kept after merge they go stale and contradict the code, so the spec is the record.
## 2026-10-14 — Implementation details: server API, Webtoons fetch forms
**Status:** accepted
**Context:** Decisions made while implementing plan 001 that the spec leaves open.
**Decision:**
- `createApp(env)` returns the handler (spec §4.10 says `handle(req, env)`) so rate-limit state is bound once.
- `/api/*` accepts `Origin: https://mangabaka.org` besides our own, for the userscript.
- Behind the proxy, the rate limiter keys on the **last** `X-Forwarded-For` entry; earlier ones come from the client.
- `/api/fetch` buffers responses up to 20 MB instead of streaming: a simple cap; pages and covers are far smaller.
- Webtoons is fetched as `/-/-/-/list?title_no=N` (official) or `/-/canvas/-/list?title_no=N` (canvas); the wrong form is a 404, so the other is tried. A made-up slug made Webtoons hang, so the user's own path is never fetched.
- Test fixtures are marked `-text` in `.gitattributes` to stay byte-exact.
**Alternatives:** Stream `/api/fetch` (needs a counting stream for the cap); trust the first `X-Forwarded-For` entry (spoofable).
**Consequences:** Reverse proxies must append to `X-Forwarded-For` (see `docs/ARCHITECTURE.md`).
## 2026-10-14 — Spec amendments from testing the real services
**Status:** accepted (amends spec 001 §2.2–§4.5)
**Context:** Before planning, we parsed real pages and downloaded every cover for Tower of God
(current + Wayback every 6 months, 2015–2026) and the canvas example; the user reviewed them in
`samples/` (gitignored). Findings: Wayback answered `429` "suspected abusive bot traffic" to a fake
Chrome User-Agent but nothing at all to an honest one at one request per 10 s; most historical
`og:image`s are character or landing-page art, not covers; MangaBaka requires exactly one
`is_default` per series; the public images endpoint is paginated and lacks `is_default`.
**Decision:**
- History granularity (every 1 / 3 / 6 / 12 months, default 6) over the span of captures, one
snapshot per bucket — the user's idea, replacing "at most 60 snapshots".
- Wayback gets `mangabaka-seeder/<version> (+repo URL)` as User-Agent and one request per 10 s; a
snapshot that fails after retries stops the history and keeps partial results.
- Images taller than 1.6 : 1 are "landing-page art": type `other`, not selected by default.
- "Make this the series' main cover" is a checkbox, on by default, warning that it replaces the
main cover for every language.
- Retries only for `GET`/`HEAD`, never `POST`.
**Alternatives:** A fixed snapshot limit (arbitrary; either misses years or wastes requests);
spoofing a browser more convincingly (dishonest, and the honest UA simply works); dropping tall
art entirely (it is genuine Webtoons art some editors may want).
**Consequences:** A default run costs ~4–5 minutes of Wayback time, so the UI needs progress and
cancel. Our server makes every user's Wayback requests from one IP — if that gets blocked, the
userscript (user's own IP) still works.
## 2026-10-14 — Size cap on the covers folder; refuse when full, never evict
**Status:** accepted (extends "Server storage is a plain folder" below)
**Context:** The user asked for storage limits on the local folder. The site is open (no gate), so
uploads must not be able to fill the server's disk.
**Decision:** `MBSEED_STORAGE_MAX_BYTES` (default 1 GiB) caps the folder. Uploads past it get 507 and
the page offers litterbox/catbox instead. Files are never deleted before their 30 days.
**Alternatives:** Evict the oldest files (rejected: their links may be in requests still awaiting
review, which would break them silently); per-IP upload quotas (deferred until abuse shows up).
**Consequences:** Under heavy use the server host becomes unavailable until the sweep frees space.
## 2026-10-14 — Server storage is a plain folder on our server
**Status:** accepted (replaces the S3/R2 storage in the entries below)
**Context:** Self-hosted, a bucket is extra infrastructure. The user chose: "For now, store images in
a plain folder on the server."
**Decision:** `MBSEED_STORAGE_DIR` holds `<sha256>.<ext>`; our server serves it at `/covers/…`
under `MBSEED_PUBLIC_URL`. `bun.ts` deletes files older than 30 days (by mtime) at startup and every
24 hours. Writes are temp file + rename. It sits behind the `Storage` interface.
**Alternatives:** S3-compatible bucket (MinIO, R2, B2) with a lifecycle rule — kept as a later option.
**Consequences:** The server must be publicly reachable for MangaBaka to fetch covers, and its
uptime now matters until each review is done. Folder access needs Bun APIs, so it lives in
`storage-dir.ts`, not in the web-standard `app.ts`.
## 2026-10-14 — Spec signed off; self-hosted only; password gate deferred
**Status:** accepted (narrows the entry below)
**Context:** The user signed off spec 001 and said the app will run self-hosted, and that
password-gating should wait: "if it gets abused or we see too big amounts of traffic, we will deal
with it then."
**Decision:** First version runs `server/app.ts` under `Bun.serve` on our own machine behind our
reverse proxy. No gate, no Cloudflare entry; both stay designed in the spec (§4.10, §4.11) and are
listed under Someday in `docs/TODO.md`. The handler stays web-standard so either can be added later.
**Alternatives:** Build the gate now (cost without a demonstrated need).
**Consequences:** The site is open: the host allowlist, the 20 MB cap and the per-IP rate limit are
the only protections. The rate limit needs the real client IP from our reverse proxy
(`MBSEED_TRUST_PROXY`).
## 2026-10-14 — Also support catbox/litterbox; optional password gate; self-hostable server
**Status:** accepted (extends the two entries below)
**Context:** The user asked for catbox/litterbox support as well, and for a way to password-gate the
static site "such that we could lock the service behind our own server".
**Decision:** Image hosts are `server` (our storage, 30 days, default when configured), `litterbox`
(≤ 72h; uploaded straight from the browser, which works because litterbox sends CORS headers),
`catbox` (permanent; through our server for the page, direct for the userscript) and `direct`.
The server is one web-standard `fetch` handler with two thin entries: Cloudflare Workers (R2
binding) and `Bun.serve` (S3-compatible bucket) for our own machine. An optional shared password
(`MBSEED_PASSWORD`) gates every route, static assets included, with an HMAC-signed session cookie,
plus `Authorization: Bearer` for the userscript. Self-hosted instances may use the reverse proxy's
auth instead. "Behind our own server" was read as covering both the built-in gate and self-hosting.
**Alternatives:** Cloudflare Access in front of the Worker (free for small teams, but
Cloudflare-only and no help when self-hosting); per-user accounts (not needed for a small group);
Basic auth (can't log out, and the browser prompt is clumsy for the userscript).
**Consequences:** On Workers, assets must run through the Worker (`run_worker_first`) or the gate
leaks the page. The userscript works without any server when using litterbox or catbox. Litterbox
links may die before a slow review; the UI warns about it.
## 2026-10-14 — Temporary image hosting: Cloudflare R2 with a 30-day expiry
**Status:** accepted (supersedes "Rehosting on catbox.moe" below)
**Context:** A static page can't upload to catbox.moe (no CORS). The user said temporary hosting is
fine with an expiry of at most a month: if moderators take longer, the user reruns.
**Decision:** The Worker rehosts into R2 at `covers/<sha256>.<ext>`, with a bucket lifecycle rule
deleting objects after 30 days. `direct` (no rehost) stays as an option.
**Alternatives:** litterbox (CORS works, but 72h maximum — likely shorter than a review);
0x0.st (CORS and MangaBaka acceptance unverified); catbox (no CORS, permanent).
**Consequences:** Needs a Cloudflare account with a card on file (R2). Expected cost is $0 on
free tiers; abuse of the Worker is the cost risk, hence the allowlist and the per-IP rate limit.
## 2026-10-14 — Static page + Cloudflare Worker, and a userscript; Bun; links given directly
**Status:** accepted (supersedes "Stack: TypeScript / Node CLI" and "Automatic series matching" below)
**Context:** The user wants a static web page first, with the CLI as secondary, Bun instead of Node,
and to pass both the MangaBaka and Webtoons links. Checked 2026-10-14: `api.mangabaka.org` sends
`access-control-allow-origin: *` (submissions too); Webtoons HTML, pstatic images and Wayback do not.
**Decision:** One pure core with an injected `Transport`. The page uses a Worker proxy (allowlisted
hosts) for Webtoons/Wayback and calls MangaBaka directly. The userscript uses `GM_xmlhttpRequest`
and needs no proxy for fetching. The user chose to pursue both front ends. Tooling is Bun
(`bun test`, `bun build`). No automatic MangaBaka search; a `title_no` link check only warns.
**Alternatives:** Local Bun server only (not a hosted page); userscript only.
**Consequences:** Two front ends to keep in step; the Worker is public infrastructure to protect.
Cloudflare IPs may be throttled by Webtoons/Wayback — the userscript doesn't have that risk.
## 2026-10-14 — Original design decisions (from the design conversation)
**Status:** partly superseded — see the two entries above
**Context:** Recorded in the former `HANDOVER.md`.
**Decision:**
- Stack: TypeScript, chosen over Go (yambs is Go) to keep a browser extension or userscript possible.
*Runtime superseded: Bun, not Node.*
- Submission path: the API when a token is available, otherwise help with manual entry.
- Which images: all — poster, square thumbnail, banner/background and foreground art.
- Historical covers through the Wayback Machine.
- Main cover: the current poster (square for canvas) gets `is_default: true`, existing covers `false`.
- Rehosting on catbox.moe by default. *Superseded: R2, 30 days.*
- Content rating chosen by the user, default `safe`; mature titles show a warning.
- Image bytes kept in memory; sizes up to ~650 KB seen; 20 MB cap.
- Extensibility: new sources and image hosts are one module plus one registry line.
- Safety: nothing uploaded or sent without explicit user action; `save_mode: "review"`.
- Not now: fields other than images; following language editions.
- Automatic series matching via `/v2/series/match`. *Superseded: links given directly.*
**Alternatives:** not recorded
**Consequences:** See the spec.