docs: record conditional updated_at, latest-chapter tracking, favourites
Documents why updated_at moves only on reading progress and why PUT therefore returns the stored row, why "latest chapter" is found from the browser rather than the backend, and its limits — a bookmark is as current as its last check, and nothing here can be instant. Also corrects two stale claims: asurascans.com is the current domain, and asuracomic.net deep links now 301 to its root rather than the matching path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 `<site>:<series_id>` (`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`)
|
||||
|
||||
@@ -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 `<site>:<series_id>` — 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/<slug-hash>` | `/comics/<slug-hash>/chapter/<n>` | `<slug-hash>` |
|
||||
| **Asura** (`asurascans.com`) | `/comics/<slug-hash>` | `/comics/<slug-hash>/chapter/<n>` | `<slug-hash>` |
|
||||
| **Demonic** (`demonicscans.org`) | `/manga/<slug>` | `/title/<slug>/chapter/<n>/<page>` (`chaptered.php?manga=<id>&chapter=<n>` 301s here) | `<slug>` |
|
||||
|
||||
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 `<Title> Chapter N`.
|
||||
- Demonic's `<slug>` is identical on `/manga/…` and the canonical `/title/…`
|
||||
|
||||
Reference in New Issue
Block a user