diff --git a/README.md b/README.md index 7dee664..cc24b0f 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ A command-line version (Bun) may follow. 2. **Collect covers.** - **Current:** the portrait poster, the square thumbnail, and the banner and hero art. - - **Historical:** cover images in archived copies of the series page on the + - **Historical** (tick "Old covers from the Wayback Machine"; off by default, + it takes minutes): cover images in archived copies of the series page on the [Wayback Machine](https://web.archive.org), up to one snapshot per month. 3. **Check each image.** It downloads each image to get its size and hash, classifies it by shape (portrait → `volume`, square → `other`, wide → @@ -55,11 +56,19 @@ A command-line version (Bun) may follow. A link must still work when a MangaBaka moderator looks at the request. If it has expired by then, run the tool again. 6. **Hand it to you.** - - **Without API access (most users):** copy the hosted URLs and paste them - into MangaBaka's cover form for the series (the page links to it). + - **Without API access (most users):** press **Download selected (ZIP)** for + the cover files plus a `covers.txt` with each one's type, rating and note, + and upload them in MangaBaka's cover form for the series (the page links to + it). No rehosting needed. Or rehost them and paste the hosted URLs instead. - **With a personal access token:** the tool shows MangaBaka's preview diff and, after you confirm, submits the request for review. +The page keeps a **history** of the series you searched, in your browser: the +covers, your ticks, notes and ratings, and what you did with them (downloaded, +rehosted, filed). Pick one from **History** or the start screen to reopen it +instantly; **Search again** refreshes it and keeps your edits. It holds the 30 +latest series (up to 300 MB) and stays in this browser only. + Nothing is uploaded or sent to MangaBaka without your action, and submissions always go to review. You choose the content rating (default `safe`); titles Webtoons marks as mature show a warning. Your token is sent only to diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 9bc4517..a08f932 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -12,8 +12,10 @@ self-hosted Bun server (primary), a userscript, and later a Bun CLI. | Path | Responsibility | |---|---| | `src/core/` | Pure pipeline: sources, Wayback history, image inspection, dedupe, seed, MangaBaka client. No DOM, Bun, Node or `GM_*` APIs. | -| `src/core/session.ts` | Review state shared by the page and the userscript: cards, main cover, rehosting, manual text, token flow (preview → confirm → save for review). | -| `src/web/` | Built. The page ("Obi Shelf" design, see `DESIGN.md`): `template.ts` markup shared with the userscript panel, `app.ts` wiring (`mountApp`), `render.ts` shelf and timeline views, `view-model.ts` pure helpers, `transport.ts` (direct to MangaBaka/litterbox, everything else via `/api/fetch`), `styles.css` scoped for a shadow root, `dark-reader.ts` (switches to the page's own dark theme and locks Dark Reader out when that extension is darkening the page). | +| `src/core/session.ts` | Review state shared by the page and the userscript: cards, main cover, rehosting (cards remember host, cache key and expiry), manual text, the download bundle, `restore` from history, token flow (preview → confirm → save for review). | +| `src/core/zip.ts`, `download.ts` | Store-only ZIP writer (CRC-32, UTF-8 names); the download bundle: file names `NN_role_lang[_YYYY]_WxH.ext` and `covers.txt` (spec 002 §2). | +| `src/core/saved.ts` | History model (spec 002 §3): `SavedSearch` (plain data, source by id), `toSaved`/`fromSaved` (drops expired links), `carryEdits` (search again), `pruneIds` (30 entries, 300 MB), `HistoryStore` interface and an in-memory one. | +| `src/web/` | Built. The page ("Obi Shelf" design, see `DESIGN.md`): `template.ts` markup shared with the userscript panel, `app.ts` wiring (`mountApp`), `render.ts` shelf and timeline views, `view-model.ts` pure helpers, `transport.ts` (direct to MangaBaka/litterbox, everything else via `/api/fetch`), `styles.css` scoped for a shadow root, `save-file.ts` (Blob download, works in the panel's shadow root), `history-db.ts` (IndexedDB `HistoryStore`; null when storage is refused), `recent.ts` (History list and the start screen's recent shelf; the page only), `dark-reader.ts` (switches to the page's own dark theme and locks Dark Reader out when that extension is darkening the page). | | `src/server/` | Built. `app.ts`: web-standard handler (`createApp(env)`) — static assets, `/api/config`, `/api/fetch` allowlisted proxy (redirects re-checked, 20 MB cap), `/api/upload` (store or catbox; takes a source URL, never bytes), `/covers/*`. `storage.ts` (interface, in-memory) and `storage-dir.ts` (folder, size cap, 30-day sweep). `config.ts` (env, client IP), `rate-limit.ts`, `assets-path.ts`. `bun.ts`: thin `Bun.serve` entry. Gate and Workers entry deferred. | | `src/userscript/` | Built. `gm.ts`: `GM_xmlhttpRequest` transport (no proxy; honest User-Agent for Wayback) and GM storage. `main.ts`: a "Seed covers" launcher on MangaBaka series pages (follows client-side navigation) opening the page's app in a shadow-root panel; optional server URL in its settings. Built by `scripts/build-userscript.ts` into `dist/web/mbseed.user.js`, served at `/mbseed.user.js`. | | `src/cli/` | Later: Bun CLI. | @@ -23,8 +25,10 @@ self-hosted Bun server (primary), a userscript, and later a Bun CLI. `runCovers` (pipeline.ts): links → MangaBaka series, links and images (direct) → `source.resolve` and `currentCovers` → `waybackHistory` (optional) → `inspectAll` (bytes, size, sha256, shape) → `dedupeByHash` → `splitExisting` against MangaBaka → `pickDefault`. `Session` (session.ts) turns that into review cards; the -user edits them, then `rehost` (explicit) → `manualText` for the cover form, or `preview` → confirm → `submit` -(`save_mode: "review"`). +user edits them, then `bundle` → ZIP download, or `rehost` (explicit) → `manualText` for the cover form, or +`preview` → confirm → `submit` (`save_mode: "review"`). On the page each search is saved to IndexedDB +(`toSaved` → `saveAndPrune`) after the run, edits, rehosting, download and filing; opening an entry runs +`fromSaved` → `Session.restore`, and a new search of a saved series runs `carryEdits`. ## Running the server `bun run build` then `bun run start`. Environment: diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index dce28a8..1fc644e 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -16,6 +16,25 @@ 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-15 — Download as a ZIP, and a history in IndexedDB (spec 002) +**Status:** accepted +**Context:** First user review: contributors without a token want the cover files to upload by hand, and a +way back to series they searched before. Design approved in chat (instant restore; history on the page only; +one ZIP). +**Decision:** +- Our own store-only ZIP writer (`core/zip.ts`, ~100 lines): covers don't compress, and it avoids a + dependency. Downloads go through a Blob URL and ``; this works in the userscript's shadow root + on mangabaka.org (checked in Chrome under the site's CSP), so `GM_download` isn't needed. +- History lives in IndexedDB on the page's origin (`summaries` for the list, `searches` with the bytes), + one entry per MangaBaka series, 30 entries / 300 MB, newest always kept. Stored as plain data with the + source by id (and its name for the list). Expired litterbox/server links are dropped on restore; cards + record `hosting` (host, cache key, expiry) when rehosted. +- Searching a saved series again carries the user's edits over by SHA-256, and keeps the entry's marks. +**Alternatives:** A ZIP library (fflate) — a new dependency for ~100 lines; history in localStorage (5 MB, +strings) or re-running the search on open (minutes with Wayback); history on our server (privacy, accounts). +**Consequences:** History is per browser and lost with site data. Old entries can show covers MangaBaka has +since gained; filing always fetches a fresh snapshot, and the "Found …" line shows the age. + ## 2026-10-15 — Wayback history starts off **Status:** accepted (amends spec 001 §4.9 "include history (on)") **Context:** First user review: a Wayback run takes minutes at the 10 s pace, while the current covers diff --git a/docs/TODO.md b/docs/TODO.md index 0645fd3..07d8935 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -27,6 +27,8 @@ Design: [specs/001-webtoons-covers.md](specs/001-webtoons-covers.md). - [ ] Mobile layout (the user deferred it, 2026-10-15): the page stacks under 1000px but was only checked at 760px. ## Verify with real services (once possible) +- [ ] Download from the userscript panel in **Firefox** with Tampermonkey/Violentmonkey (checked in Chrome only); + if the Blob link is blocked there, add `GM_download` (spec 002 §6). - [ ] Which of our servers and domain hosts it (`MBSEED_PUBLIC_URL`); where the covers folder lives. - [ ] Does catbox accept uploads from our server? - [ ] Does MangaBaka accept our storage URLs, litterbox/catbox URLs, and diff --git a/test/history-db.test.ts b/test/history-db.test.ts new file mode 100644 index 0000000..d18c691 --- /dev/null +++ b/test/history-db.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from "bun:test"; +import { openHistory } from "../src/web/history-db"; + +test("no IndexedDB, or a browser that refuses it: the page runs without history", async () => { + expect(await openHistory(undefined)).toBeNull(); + const refusing = { open: () => { throw new DOMException("blocked", "SecurityError"); } } as unknown as IDBFactory; + expect(await openHistory(refusing)).toBeNull(); +});