11 KiB
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 sayshandle(req, env)) so rate-limit state is bound once./api/*acceptsOrigin: https://mangabaka.orgbesides our own, for the userscript.- Behind the proxy, the rate limiter keys on the last
X-Forwarded-Forentry; earlier ones come from the client. /api/fetchbuffers 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
-textin.gitattributesto stay byte-exact. Alternatives: Stream/api/fetch(needs a counting stream for the cap); trust the firstX-Forwarded-Forentry (spoofable). Consequences: Reverse proxies must append toX-Forwarded-For(seedocs/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, neverPOST. 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 coversfalse. - 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.