4229c179b0
Two intertwined changes — the rebrand and the novel library were developed on
the same branch because the novel UI plumbing is part of the new "Bookmark
Manager" wordmark in the web shell.
## What it does
- **Rebrand**: MangaBM → BookmarkManager across the Go module, compose stack,
env vars, Traefik hostnames, container/image names, userscript storage
prefixes (`mangabm:cache` → `bmgr:manga:cache`, `mangabm:queue` → `bmgr:manga:queue`),
and docs.
- **Novel library**: same backend, two libraries. New `kind` column splits
bookmarks into `manga` / `novel`; PUT validates it. Two userscripts:
- `manga-bookmark.user.js` — unchanged behaviour, just stamps its own `kind`.
- `novel-bookmark.user.js` — separate Violentmonkey install with adapters
for **novelfull.com** (polled via headless browser — Cloudflare JS
challenge) and **lightnovelworld.net** (polled via plain TLS).
- **Web UI**: library switch on the app shell. Login art, libswitch, and
novel-site colours from the Cinder design snapshot.
## Plumbing
- `addedColumns` ALTER for `kind` runs on first start after upgrade; every
pre-existing row is backfilled to `'manga'`. No manual SQL, no down-time.
- `ALLOWED_ORIGINS` gains the two novel sites.
- New `NOVEL_USERSCRIPT_PATH` env (default `/userscript/novel-bookmark.user.js`),
bindmounted alongside the manga script.
- Traefik router names `mangabm*` → `bmapi*` / `bmweb*`.
## Test status
- `go test ./...` — green
- `node --test userscript/test/logic.test.js` — 34 pass
- `node --test userscript/test/novel-logic.test.js` — 11 pass
- `node --check` on both userscripts — clean
## Notes for the redeploy
.env keys were renamed (`MANGA_API_HOST` → `BOOKMARK_API_HOST`,
`MANGA_WEB_HOST` → `BOOKMARK_WEB_HOST`). Update DNS / Traefik labels on the
prod override before pulling, otherwise the public hostnames go dark.
See the redeploy instructions I'll post next to this PR.
Reviewed-on: #15
Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
4.7 KiB
4.7 KiB
Guidance for Claude Code working under userscript/. See root CLAUDE.md for the project-wide architecture diagram, hard constraints, and design system.
Userscript structure (single IIFE, manga-bookmark.user.js)
- Site adapters — one per host,
detect(location, document)return pagetype+ IDs. Identify type/IDs from URL regex (most stable); pulltitle/coverfromog:title/og:imagemeta tags, not CSS classes. - API client —
apiGet/apiPut/apiDeletewith bearer header;localStoragekeybmgr:manga:cachefor instant render + offline fallback. - Progress logic — auto-upsert
last_chapteronly whenchapterNum >= stored last_chapter_num(re-reading old chapters must not regress progress; unparseable -> set current). Manual panel override forces any value. - Retry queue — every write go through
pushBookmark/pushDelete, so failed mutation park inlocalStorage(bmgr:manga:queue) and replayed on next navigation, reconnect, orrefresh(). Entries are markers ({key, op, sendStatus, attempts}), never payloads — body read from cache at send time, so one entry per key give ordering and coalescing for free.sendStatusis sticky: while archive pending, later writes to that key keep carrying bucket, which stop successful in-between write from silently un-archiving series.refresh()drains before it fetches and overlays anything still pending, so list never flaps. 400 drops entry, 401 abort pass and keep queue, and transient failures retry to cap of 10. Latest-chapter writes deliberately stay out of queue. Seedocs/superpowers/specs/2026-07-27-offline-retry-queue-design.md. - UI — rendered inside Shadow DOM root to isolate from site CSS
(critical on mobile). Three tabs (All / Favourites / Archived) and row of
link chips to web UI and both manga sites;
WEB_BASEsits in CONFIG block next toAPI_BASE. FAB is7 × 44edge tab whose hit area widened to28 × 72by invisible#hitchild;#fabmust keeptouch-action: noneand must not regainoverflow: hidden. Sincetouch-actionresolved at gesture start, strip can't be both browser-scrolled and script-dragged, somakeDraggablesplits by intent: swipe from#hitscrolls viawindow.scrollBy, hold ofARM_MSarms reposition drag, visible sliver drags with no hold. Seedocs/superpowers/specs/2026-07-28-edge-tab-hitbox-design.md. - SPA navigation — Asura is Astro, client-routed on comic/chapter pages: patch
history.pushState/replaceState+ listenpopstate, re-rundetect()on URL change so auto-update fire without reload. Demonic uses classic reloads (initialdocument-idlerun suffice).
Live URL shapes (verified 2026-07-26, may drift — re-check against live pages before trust)
- asurascans.com: series
/comics/<slug>(slug carries trailing site-wide build-hash suffix, e.g.-059befe1, that rotates on every redeploy), chapter/comics/<slug>/chapter/<n>.seriesIdmust strip hash (/-[0-9a-f]{8}$/,stripBuildHashin userscript,asuraBuildHashin backend); URLs keep full slug — stale-hash URLs 302 to current ones. Astro-rendered; chapter links present in raw server HTML. - demonicscans.org: series
/manga/<slug>(slug may URL-encode punctuation, e.g.%2527for'), chapter/title/<slug>/chapter/<n>/<page>(olderchaptered.php?manga=<id>&chapter=<n>form still exists as redirect, what series-page chapter-list anchors link through). Encodings (incl. triple-encoded punctuation like%25252D) identical on /manga/ and /title/ pages, so decode-once seriesIds match — verified 2026-07-28. - novelfull.com (novel script): series
/<slug>.html, chapter/<slug>/chapter-<n>[-<title-slug>].html. Noog:*tags at all — title fromh3.title(series) ora.truyen-title(chapter), cover frommeta[name="image"]. Behind a Cloudflare JS challenge no TLS fingerprint clears, so the backend polls it through the headless browser. - lightnovelworld.net (novel script): series
/novel/<slug>/, chapter/<slug>-chapter-<n>/— flat, at the site root.h1.entry-titleis the clean title on a series page and<Title> Chapter <n>on a chapter page. Chapter pages carry noog:image. Its series page lists every chapter with an absolute href, so the backend polls it with the plain TLS client.
Second script: novel-bookmark.user.js
A copy of the manga script with two adapters, LIBRARY = "novel" and
STORE_PREFIX = "bmgr:novel:". No migration loop (this script has no previous
installation to carry keys over from). Installed alongside the manga script;
both write to the same backend with the same LIBRARY column discriminating
them.