Files
mangabaka-seeder/docs/DECISIONS.md
T

11 KiB
Raw Blame History

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:images 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.

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.