diff --git a/CLAUDE.md b/CLAUDE.md index b4dd55b..453d735 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ Greenfield. Only `plans/mangaBookmark.md` exists — no code yet. That plan is t ## What this is -A manga read-progress tracker for a user reading on **asuracomic.net** (formerly asurascans.com) and **demonicscans.org** from **Bromite** (mobile Chromium). A userscript injects on-page UI (floating button + slide-in panel) and syncs progress to a self-hosted Go backend so bookmarks unify across both sites and across devices. +A manga read-progress tracker for a user reading on **asurascans.com** (the current domain; asuracomic.net is the older one) and **demonicscans.org** from **Bromite** (mobile Chromium). A userscript injects on-page UI (floating button + slide-in panel) and syncs progress to a self-hosted Go backend so bookmarks unify across both sites and across devices. ## Hard constraints (these drive the design — do not violate) @@ -28,7 +28,8 @@ Bromite userscript (isolated world, per-site adapters, localStorage cache) - **Backend** (`backend/`): stdlib `net/http` (3 routes, no framework) + `modernc.org/sqlite` (pure Go, `CGO_ENABLED=0` -> static binary -> distroless/scratch image). The reverse proxy terminates TLS; the Go service listens plain `:8080`. - **Single-user store.** One `bookmarks` table keyed `:` (`asura`|`demonic`). Sync is **last-write-wins**. Schema and endpoint list are in the plan. -- **Endpoints:** `GET /bookmarks`, `PUT /bookmarks/{key}` (upsert, server sets `updated_at`), `DELETE /bookmarks/{key}`, `GET /healthz` (no auth). +- **Endpoints:** `GET /bookmarks`, `PUT /bookmarks/{key}` (upsert; see `updated_at` rule below), `DELETE /bookmarks/{key}`, `GET /healthz` (no auth). +- **`updated_at` drives list order, so it moves only on real reading progress:** the server applies its timestamp when the row is new or `last_chapter_num` changes, and otherwise keeps the stored value — favouriting a series or recording a newly published chapter must not reorder the list. `PUT` therefore returns the row **as stored**, and clients must adopt that response rather than their own payload. See `plans/2026-07-25-bookmark-list-favorites-design.md` §4. - **Config via env:** `API_TOKEN`, `ALLOWED_ORIGINS` (comma list), `DB_PATH` (default `/data/bookmarks.db`), `PORT` (default `8080`). ### Userscript structure (single IIFE, `manga-bookmark.user.js`) diff --git a/README.md b/README.md index 33a9600..bb884be 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,20 @@ Bromite userscript (isolated world, Shadow DOM UI, localStorage cache) | Method | Path | Auth | Description | |--------|------|------|-------------| | `GET` | `/bookmarks` | Bearer | All bookmarks (single-user). | -| `PUT` | `/bookmarks/{key}` | Bearer | Upsert one series; server sets `updated_at`. | +| `PUT` | `/bookmarks/{key}` | Bearer | Upsert one series; returns the row as stored. | | `DELETE` | `/bookmarks/{key}` | Bearer | Remove one. | | `GET` | `/healthz` | none | `200 ok`. | `key` is `:` — e.g. `asura:trash-of-the-counts-family-f886a8af` or `demonic:Infinite-Level-Up-in-Murim`. Sync is last-write-wins. +`updated_at` orders the bookmark list, so it moves only on real reading +progress: the server applies its timestamp when the row is new or +`last_chapter_num` changes, and otherwise keeps the stored one. Favouriting a +series or recording a newly published chapter therefore leaves the order alone. +Because the timestamp a client sends is only a candidate, `PUT` echoes the row +**as stored** and clients adopt that rather than their own payload. + ### Develop / test ```bash @@ -135,12 +142,42 @@ desktop for faster testing — install the same file unchanged. progress; unparseable numbers set the current chapter). - **Manual override**: panel → **Edit** on any row forces a specific chapter. - **Continue**: jumps to the last-read chapter (or the series page). +- **Latest chapter**: rows read `Read: … · Latest: …` once the newest published + chapter is known and it is ahead of your progress. See below for how that is + found. +- **Favourites**: the ☆ on any row toggles it; the **★ Favourites** tab narrows + the list. Favourited series still appear under **All**. The flag syncs, so it + follows you across devices; the chosen tab does not persist. - Bookmarks made on Asura appear when the panel is opened on Demonic, and vice versa — the backend is the shared store. +Neither favouriting nor learning a new chapter reorders the list — only reading +progress does. + Offline / backend down: changes are cached in `localStorage` and retried on the next successful load (last-write-wins). +#### How "latest chapter" is found + +Only a series page lists every chapter (a reader page links just its +neighbours), and the backend cannot fetch either site — Cloudflare blocks +server-side requests, and neither site offers an API or feed to poll. So the +userscript does the looking, from your own browser session: + +- Opening a bookmarked series page records its newest chapter directly. +- Otherwise it fetches series pages in the background — **same-origin only**, so + browsing Asura refreshes Asura bookmarks and Demonic refreshes Demonic. One + series per navigation, and at most one check per series every 4 hours + (`LATEST_CHECK_BATCH` / `LATEST_CHECK_THROTTLE_MS`). Failures are silent and + simply retried after the window. + +Freshness is tracked per device in `localStorage` under `mangabm:lastchecked` +and is deliberately not synced, since each device checks on its own. + +This means a bookmark is as current as its last check — not the moment a +chapter drops. Nothing can be instant here: neither site offers push, feeds, or +an API. + --- ## Adapter reference (verified live 2026-07-24) @@ -150,10 +187,15 @@ The site adapters key everything off URL regex, with `title`/`cover` from | Site | Series URL | Chapter URL | `series_id` | |------|-----------|-------------|-------------| -| **Asura** (`asurascans.com`; `asuracomic.net` 301s here) | `/comics/` | `/comics//chapter/` | `` | +| **Asura** (`asurascans.com`) | `/comics/` | `/comics//chapter/` | `` | | **Demonic** (`demonicscans.org`) | `/manga/` | `/title//chapter//` (`chaptered.php?manga=&chapter=` 301s here) | `` | Notes: +- **`asuracomic.net` deep links are dead (re-checked 2026-07-25).** They 301 to + the `asurascans.com` **root**, discarding the path, at the edge — before the + userscript gets a document — so nothing client-side can rescue them. Reach + series through `asurascans.com`. The host stays matched in case the redirect + starts preserving paths again. - Asura `og:title` carries a `Chapter N - Read Online \| Asura Scans` suffix that the adapter strips; Demonic chapter `og:title` is ` Chapter N`. - Demonic's `<slug>` is identical on `/manga/…` and the canonical `/title/…`