diff --git a/.env.example b/.env.example index 1fb87cc..51f093b 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ ALLOWED_ORIGINS=https://asuracomic.net,https://asurascans.com,https://demonicsca # --- Prod override (Traefik) only --- # Subdomain Traefik routes to this service (required by the prod override). -# MANGA_API_HOST=manga-api.example.com +# BOOKMARK_API_HOST=bookmark-api.example.com # Traefik's docker network name, if not "proxy". # PROXY_NETWORK=proxy # Traefik HTTPS entrypoint + cert resolver names, if yours differ from these. @@ -18,7 +18,7 @@ ALLOWED_ORIGINS=https://asuracomic.net,https://asurascans.com,https://demonicsca # TRAEFIK_CERTRESOLVER=le # --- Web UI --- -# Password for the browser UI at https://$MANGA_WEB_HOST. Leave unset to +# Password for the browser UI at https://$BOOKMARK_WEB_HOST. Leave unset to # disable the web UI entirely (the routes are not registered at all). # Generate one: openssl rand -base64 18 WEB_PASSWORD= @@ -27,8 +27,8 @@ WEB_PASSWORD= # whether or not WEB_PASSWORD is set). Left commented on purpose: an example # value here would be a silent wrong-hostname fallback, and Traefik would # publish the UI router on a domain you do not own. The same container also -# answers on MANGA_API_HOST for the userscript's API. -# MANGA_WEB_HOST=manga.example.com +# answers on BOOKMARK_API_HOST for the userscript's API. +# BOOKMARK_WEB_HOST=bookmark.example.com # --- Latest-chapter poller --- # The backend re-checks each bookmarked series' newest published chapter on its diff --git a/AGENTS.md b/AGENTS.md index c7dcc7c..abd4d64 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,7 +49,7 @@ Smoke test: `curl` endpoints with `Authorization: Bearer `; confirm `OPTI ## Design system Web UI + userscript panel follow **Cinder**, rules in `docs/design-system.md` -— source of truth Claude Design project `mangaBookmark Web UI` +— source of truth Claude Design project `BookmarkManager Web UI` (`969ac210-fe02-4c01-ae1b-9a271dcc779a`). Read it before touching `backend/internal/web/static/style.css`, `backend/internal/web/templates/*`, or userscript `TEMPLATE`/`CSS`. Core law: **ember means new chapter only** — no other diff --git a/CLAUDE.md b/CLAUDE.md index 2363ed0..c3809e1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,7 +49,7 @@ Smoke test: `curl` endpoints with `Authorization: Bearer `; confirm `OPTI ## Design system Web UI + userscript panel follow **Cinder**, rules in `docs/design-system.md` -— source of truth Claude Design project `mangaBookmark Web UI` +— source of truth Claude Design project `BookmarkManager Web UI` (`969ac210-fe02-4c01-ae1b-9a271dcc779a`). Read it before touching `backend/internal/web/static/style.css`, `backend/internal/web/templates/*`, or userscript `TEMPLATE`/`CSS`. Core law: **ember means new chapter only** — no other diff --git a/DEPLOY.md b/DEPLOY.md index eb3f5f5..b5911e9 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -10,8 +10,8 @@ ACME/cert resolver, and control a domain. - Docker + Docker Compose on the server. - A Traefik instance watching a Docker network (default name assumed: `proxy`). -- DNS: an `A`/`AAAA` record for `manga-api.` pointing at the server. -- The repo copied to the server, e.g. `/opt/mangabm/` (needs `backend/`, +- DNS: an `A`/`AAAA` record for `bookmark-api.` pointing at the server. +- The repo copied to the server, e.g. `/opt/bookmarkmanager/` (needs `backend/`, `docker-compose.yml`, `docker-compose.prod.yml`, `.env.example`). Confirm the Traefik network exists (create if not): @@ -25,7 +25,7 @@ docker network ls | grep proxy || docker network create proxy ## 1. Configure `.env` ```bash -cd /opt/mangabm +cd /opt/bookmarkmanager cp .env.example .env ``` @@ -39,10 +39,10 @@ API_TOKEN= ALLOWED_ORIGINS=https://asuracomic.net,https://asurascans.com,https://demonicscans.org,https://comix.to,https://kagane.to # Required for the Traefik override. Both have no fallback — compose refuses -# to start without them. MANGA_WEB_HOST is required even if you never set +# to start without them. BOOKMARK_WEB_HOST is required even if you never set # WEB_PASSWORD; see 1b. -MANGA_API_HOST=manga-api.violetcrown.my.id -MANGA_WEB_HOST=manga.violetcrown.my.id +BOOKMARK_API_HOST=bookmark-api.violetcrown.my.id +BOOKMARK_WEB_HOST=bookmark.violetcrown.my.id # Only if your Traefik setup differs from these defaults: # PROXY_NETWORK=proxy @@ -67,13 +67,13 @@ grep -E '^API_TOKEN=' .env # copy this — the userscript needs the same value The browser UI is served by the same container on a second hostname. -1. Add a DNS `A`/`AAAA` record for `manga.` pointing at the server — - the same address as `manga-api.`. +1. Add a DNS `A`/`AAAA` record for `bookmark.` pointing at the server — + the same address as `bookmark-api.`. 2. Set both variables in `.env`: ```ini - MANGA_WEB_HOST=manga.violetcrown.my.id + BOOKMARK_WEB_HOST=bookmark.violetcrown.my.id WEB_PASSWORD= ``` @@ -88,16 +88,16 @@ The browser UI is served by the same container on a second hostname. ```bash docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build - curl -s -o /dev/null -w '%{http_code}\n' https://manga.violetcrown.my.id/ + curl -s -o /dev/null -w '%{http_code}\n' https://bookmark.violetcrown.my.id/ ``` Expected `200`, serving the login page. Leaving `WEB_PASSWORD` unset is safe: the web routes are not registered and `/` -returns 404. The userscript's API on `MANGA_API_HOST` is unaffected either way. +returns 404. The userscript's API on `BOOKMARK_API_HOST` is unaffected either way. -`MANGA_WEB_HOST` itself is required by the prod override regardless — like -`MANGA_API_HOST`, its Traefik label has no fallback, so `docker compose up` +`BOOKMARK_WEB_HOST` itself is required by the prod override regardless — like +`BOOKMARK_API_HOST`, its Traefik label has no fallback, so `docker compose up` refuses to start without it even if `WEB_PASSWORD` is unset and the web UI is otherwise dormant. @@ -116,9 +116,9 @@ This merges the base file (build/image/env/volume) with the prod override (no host port, Traefik network + router labels). Always pass **both** `-f` flags — the prod file is not standalone. -Two services come up: `manga-api` (the backend) and `headless-shell`, a CDP +Two services come up: `bookmark-api` (the backend) and `headless-shell`, a CDP sidecar the poller uses to fetch kagane (behind a Cloudflare JS challenge). -It has no published port — only `manga-api` can reach it, over +It has no published port — only `bookmark-api` can reach it, over `BROWSER_WS_URL`. Missing or unreachable, the poller just skips kagane and logs it; nothing else is affected. @@ -126,7 +126,7 @@ Check it's up and healthy: ```bash docker compose -f docker-compose.yml -f docker-compose.prod.yml ps -docker logs manga-api --tail 20 # expect: "listening on :8080 ..." +docker logs bookmark-api --tail 20 # expect: "listening on :8080 ..." ``` --- @@ -137,21 +137,21 @@ Give Traefik a few seconds to issue the cert, then: ```bash # Health (no auth) — must be valid TLS, no cert warning. -curl -s https://manga-api.violetcrown.my.id/healthz # -> ok +curl -s https://bookmark-api.violetcrown.my.id/healthz # -> ok # Auth enforced. curl -s -o /dev/null -w '%{http_code}\n' \ - https://manga-api.violetcrown.my.id/bookmarks # -> 401 + https://bookmark-api.violetcrown.my.id/bookmarks # -> 401 TOKEN=$(grep -E '^API_TOKEN=' .env | cut -d= -f2) curl -s -H "Authorization: Bearer $TOKEN" \ - https://manga-api.violetcrown.my.id/bookmarks # -> [] + https://bookmark-api.violetcrown.my.id/bookmarks # -> [] # CORS preflight from a real site origin. curl -s -i -X OPTIONS \ -H 'Origin: https://asurascans.com' \ -H 'Access-Control-Request-Method: PUT' \ - https://manga-api.violetcrown.my.id/bookmarks/x | grep -i access-control + https://bookmark-api.violetcrown.my.id/bookmarks/x | grep -i access-control # -> Access-Control-Allow-Origin: https://asurascans.com (+ Methods/Headers) ``` @@ -165,7 +165,7 @@ a bad cert makes the browser block the userscript's `fetch()` (mixed content). Edit the config block at the top of `userscript/manga-bookmark.user.js`: ```js -const API_BASE = "https://manga-api.yourdomain.com"; // no trailing slash +const API_BASE = "https://bookmark-api.yourdomain.com"; // no trailing slash const API_TOKEN = ""; ``` @@ -197,7 +197,7 @@ Tampermonkey/Violentmonkey for quick checks before going mobile. ## 6. Smoke-test the full loop 1. Bookmark a series on Asura. -2. `curl -s -H "Authorization: Bearer $TOKEN" https://manga-api.yourdomain.com/bookmarks` +2. `curl -s -H "Authorization: Bearer $TOKEN" https://bookmark-api.yourdomain.com/bookmarks` on the server — the series should appear. 3. Open a chapter of that series — reopen the panel; last-read updates to that chapter (auto, never regresses on older chapters). @@ -223,7 +223,7 @@ SQLite data persists in the named volume `bookmarks-data` across rebuilds. | Symptom | Likely cause / fix | |---------|--------------------| | No cert / TLS error at the domain | `TRAEFIK_ENTRYPOINT` or `TRAEFIK_CERTRESOLVER` name wrong; or DNS not resolving yet. Check `docker logs `. | -| 404 from Traefik | Service not on the `proxy` network, or `MANGA_API_HOST` mismatch. Confirm `docker network inspect proxy` lists `manga-api`. | +| 404 from Traefik | Service not on the `proxy` network, or `BOOKMARK_API_HOST` mismatch. Confirm `docker network inspect proxy` lists `bookmark-api`. | | `fetch` fails in the userscript, `curl` works | Origin missing from `ALLOWED_ORIGINS`, or mixed content (backend not HTTPS). | | 401 with the right token | Trailing space/newline in `API_TOKEN`; regenerate and restart. | | Panel button absent | URL didn't match an adapter, or user scripts disabled in Bromite. | @@ -243,7 +243,7 @@ auto-updates come from it too. Install once, on the phone (Cromite + Violentmonkey): ``` -https://manga-api./u//manga-bookmark.user.js +https://bookmark-api./u//manga-bookmark.user.js ``` Open that URL in Cromite; Violentmonkey offers to install it. The token is in diff --git a/PRODUCT.md b/PRODUCT.md index 3540e61..a4ec0b0 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -35,7 +35,7 @@ Not a public reading tracker or social app — a private, self-hosted sync layer ## Brand Commitments -- Name: **mangaBookmark**. +- Name: **BookmarkManager**. - **Dark-first is binding**: current dark-by-default / light-follows-system-preference behavior must be preserved as a design constraint, not just a starting default, because reading happens at night. ## Evidence on Hand diff --git a/README.md b/README.md index 95be8e6..2174af3 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ curl -s -i -X OPTIONS -H 'Origin: https://asurascans.com' \ ### Deploy behind your reverse proxy -Route `https://manga-api.` → the service on `:8080` (TLS at the proxy). +Route `https://bookmark-api.` → the service on `:8080` (TLS at the proxy). - **Host proxy** (nginx/Caddy on the host): the base compose already binds `127.0.0.1:8080`; point the proxy `proxy_pass http://127.0.0.1:8080;`. @@ -99,7 +99,7 @@ Route `https://manga-api.` → the service on `:8080` (TLS at the proxy) ``` Set `PROXY_NETWORK` in `.env` if your network isn't named `proxy`. -Verify: `https://manga-api./healthz` returns `ok` over valid TLS (no +Verify: `https://bookmark-api./healthz` returns `ok` over valid TLS (no mixed-content), and an `OPTIONS` preflight from a real site origin returns the CORS headers. @@ -112,7 +112,7 @@ CORS headers. Edit the config block at the top of `userscript/manga-bookmark.user.js`: ```js -const API_BASE = "https://manga-api."; // no trailing slash +const API_BASE = "https://bookmark-api."; // no trailing slash const API_TOKEN = ""; ``` @@ -184,7 +184,7 @@ userscript does the looking, from your own browser session: (`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` +Freshness is tracked per device in `localStorage` under `bmgr:manga: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 diff --git a/REDEPLOY.md b/REDEPLOY.md index 5b74008..9a714c1 100644 --- a/REDEPLOY.md +++ b/REDEPLOY.md @@ -9,16 +9,16 @@ Whole thing is ~5 minutes, most of it waiting on `docker build`. Order matters: **back up before you pull.** A backup taken after a bad migration is a backup of the damage. -Paths below assume the checkout is at `/opt/mangabm`; substitute your own. The -one absolute rule about paths: **backups live in `../mangabm-backups/`**, a -sibling of the project directory (`/opt/mangabm-backups`), never inside it. It +Paths below assume the checkout is at `/opt/bookmarkmanager`; substitute your own. The +one absolute rule about paths: **backups live in `../bookmarkmanager-backups/`**, a +sibling of the project directory (`/opt/bookmarkmanager-backups`), never inside it. It sits outside the repo so `git pull`, `git clean -fd` and a bad `rm -rf` inside the checkout cannot take the backups with them. ``` /opt/ -├── mangabm/ <- the checkout (this repo) -└── mangabm-backups/ <- bookmarks-YYYYmmdd-HHMMSS.db +├── bookmarkmanager/ <- the checkout (this repo) +└── bookmarkmanager-backups/ <- bookmarks-YYYYmmdd-HHMMSS.db ``` --- @@ -26,12 +26,12 @@ the checkout cannot take the backups with them. ## 0. Preflight ```bash -cd /opt/mangabm +cd /opt/bookmarkmanager # Both -f flags, every time. The prod override is not standalone. COMPOSE="docker compose -f docker-compose.yml -f docker-compose.prod.yml" -$COMPOSE ps # manga-api should be Up +$COMPOSE ps # bookmark-api should be Up git status --short # expect empty git log --oneline -1 # note this hash — it is your rollback target df -h /var/lib/docker | tail -1 # a build needs room @@ -44,9 +44,9 @@ dirty tree fails halfway and leaves you in a worse spot than either. Create the backup directory once, and make sure it is a sibling, not a child: ```bash -mkdir -p ../mangabm-backups -BACKUP_DIR="$(cd .. && pwd)/mangabm-backups" # absolute — Docker needs it -echo "$BACKUP_DIR" # -> /opt/mangabm-backups +mkdir -p ../bookmarkmanager-backups +BACKUP_DIR="$(cd .. && pwd)/bookmarkmanager-backups" # absolute — Docker needs it +echo "$BACKUP_DIR" # -> /opt/bookmarkmanager-backups ``` --- @@ -59,7 +59,7 @@ prefixes it with the project directory: ```bash docker volume ls --filter name=bookmarks-data -# -> local mangabm_bookmarks-data +# -> local bookmarkmanager_bookmarks-data VOL=$(docker volume ls --filter name=bookmarks-data -q | head -1) ``` @@ -172,11 +172,11 @@ bindmounted read-only and read fresh per request. ```bash $COMPOSE ps # Up, and recently (re)created -docker logs manga-api --tail 20 # -> "listening on :8080 ..." +docker logs bookmark-api --tail 20 # -> "listening on :8080 ..." ``` Nothing in the log about the database or the poller failing. The image is tagged -`mangabm-backend:latest`, so the previous image is still on disk untagged — +`bookmarkmanager-backend:latest`, so the previous image is still on disk untagged — that is what makes the rollback in §6 quick. --- @@ -186,8 +186,8 @@ that is what makes the rollback in §6 quick. Same four API checks as `DEPLOY.md` §3, plus the web UI. Set the host names once: ```bash -API=https://manga-api.violetcrown.my.id -WEB=https://manga.violetcrown.my.id +API=https://bookmark-api.violetcrown.my.id +WEB=https://bookmark.violetcrown.my.id TOKEN=$(grep -E '^API_TOKEN=' .env | cut -d= -f2) curl -s $API/healthz # -> ok @@ -281,7 +281,7 @@ docker run --rm -v "$VOL":/data -v "$BACKUP_DIR":/backup alpine sh -c ' ls -l /data' $COMPOSE start -docker logs manga-api --tail 20 +docker logs bookmark-api --tail 20 curl -s -H "Authorization: Bearer $TOKEN" $API/bookmarks | head -c 200 ``` @@ -302,9 +302,9 @@ Two steps here are easy to skip and both bite: For a routine redeploy where nothing needs deciding: ```bash -cd /opt/mangabm +cd /opt/bookmarkmanager COMPOSE="docker compose -f docker-compose.yml -f docker-compose.prod.yml" -BACKUP_DIR="$(cd .. && pwd)/mangabm-backups"; mkdir -p "$BACKUP_DIR" +BACKUP_DIR="$(cd .. && pwd)/bookmarkmanager-backups"; mkdir -p "$BACKUP_DIR" VOL=$(docker volume ls --filter name=bookmarks-data -q | head -1) STAMP=$(date -u +%Y%m%d-%H%M%S) @@ -314,7 +314,7 @@ docker run --rm -v "$VOL":/data -v "$BACKUP_DIR":/backup alpine sh -c \ git pull --ff-only && $COMPOSE up -d --build && sleep 5 && -curl -sf https://manga-api.violetcrown.my.id/healthz && echo " deploy ok" +curl -sf https://bookmark-api.violetcrown.my.id/healthz && echo " deploy ok" ``` The `&&` chain is deliberate: if the backup or its integrity check fails, @@ -332,7 +332,7 @@ command can tell you the panel works on the phone. | CSS or template change did not appear | You restarted without `--build`. Assets are `//go:embed`ed. | | Font answers `application/octet-stream` | Old binary — the `.woff2` MIME registration is in `web.go`. Rebuild. | | Everyone logged out of the web UI | `API_TOKEN` or `WEB_PASSWORD` changed; sessions are derived from both. Expected, just log in again. | -| `compose` errors about `MANGA_WEB_HOST` | Run from the directory holding `.env`. Both host vars are required even when the web UI is unused. | +| `compose` errors about `BOOKMARK_WEB_HOST` | Run from the directory holding `.env`. Both host vars are required even when the web UI is unused. | | Userscript did not update on the phone | Violentmonkey polls on its own schedule; force a check. `@version` comes from the file's mtime, so confirm the pull actually touched it. | | `apk add sqlite` fails (no network) | Use the cold-copy fallback in §1 — and copy `bookmarks.db-wal` too. | | Reads work but every write fails after a restore | Restored file is root-owned; the container is uid 65532. `chown 65532:65532` it (§6). | diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e58e286..4851023 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,11 +1,11 @@ # Production override: join an existing Traefik network and let Traefik route -# manga-api. -> this service with TLS. No host port published. +# bookmark-api. -> this service with TLS. No host port published. # # docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build # # Set in .env: -# MANGA_API_HOST=manga-api.example.com # your subdomain (required) -# MANGA_WEB_HOST=manga.example.com # browser UI subdomain, same container (required) +# BOOKMARK_API_HOST=bookmark-api.example.com # your subdomain (required) +# BOOKMARK_WEB_HOST=bookmark.example.com # browser UI subdomain, same container (required) # PROXY_NETWORK=proxy # Traefik's network name, if not "proxy" # TRAEFIK_ENTRYPOINT=websecure # your HTTPS entrypoint name # TRAEFIK_CERTRESOLVER=le # your ACME/cert resolver name @@ -14,7 +14,7 @@ # docker network create proxy # if it doesn't yet services: - manga-api: + bookmark-api: # Traffic arrives over the Traefik network, not a published port. ports: !reset [] environment: @@ -33,19 +33,19 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=${PROXY_NETWORK:-proxy}" - - "traefik.http.routers.mangabm.rule=Host(`${MANGA_API_HOST:?set MANGA_API_HOST in .env}`)" - - "traefik.http.routers.mangabm.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.mangabm.tls=true" - - "traefik.http.routers.mangabm.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}" - - "traefik.http.services.mangabm.loadbalancer.server.port=8080" + - "traefik.http.routers.bmapi.rule=Host(`${BOOKMARK_API_HOST:?set BOOKMARK_API_HOST in .env}`)" + - "traefik.http.routers.bmapi.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" + - "traefik.http.routers.bmapi.tls=true" + - "traefik.http.routers.bmapi.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}" + - "traefik.http.services.bmapi.loadbalancer.server.port=8080" # Second hostname for the browser UI, same container. Traefik needs the # service named explicitly once more than one router targets it. - - "traefik.http.routers.mangabm.service=mangabm" - - "traefik.http.routers.mangaweb.rule=Host(`${MANGA_WEB_HOST:?set MANGA_WEB_HOST in .env}`)" - - "traefik.http.routers.mangaweb.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.mangaweb.tls=true" - - "traefik.http.routers.mangaweb.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}" - - "traefik.http.routers.mangaweb.service=mangabm" + - "traefik.http.routers.bmapi.service=bmapi" + - "traefik.http.routers.bmweb.rule=Host(`${BOOKMARK_WEB_HOST:?set BOOKMARK_WEB_HOST in .env}`)" + - "traefik.http.routers.bmweb.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" + - "traefik.http.routers.bmweb.tls=true" + - "traefik.http.routers.bmweb.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}" + - "traefik.http.routers.bmweb.service=bmapi" # headless-shell is untouched here: it keeps its `browser` network membership # from the base file and must never join `proxy` — that network is shared diff --git a/docker-compose.yml b/docker-compose.yml index a819f16..2eb4a39 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,16 @@ # Base stack — works standalone for local smoke testing (`docker compose up`). # The service binds 127.0.0.1:8080; a host reverse proxy (nginx/Caddy/Traefik) -# terminates TLS for manga-api. and forwards to it. +# terminates TLS for bookmark-api. and forwards to it. # # If your proxy runs in Docker on its own network, use the prod override which # attaches to that network instead of publishing a port: # docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d services: - manga-api: + bookmark-api: build: ./backend - image: mangabm-backend:latest - container_name: manga-api + image: bookmarkmanager-backend:latest + container_name: bookmark-api restart: unless-stopped environment: # API_TOKEN is required — compose refuses to start without it. @@ -63,7 +63,7 @@ services: # container's lifetime. init: true # Deliberately no `ports:` — an exposed CDP endpoint is remote code - # execution. Only manga-api, via the `browser` network below, may reach it. + # execution. Only bookmark-api, via the `browser` network below, may reach it. # Don't pass --remote-debugging-address/--remote-debugging-port here: the # image's own entrypoint (/headless-shell/run.sh) already starts Chrome on # 127.0.0.1:9223 and fronts it with a socat proxy listening on 0.0.0.0:9222. @@ -86,7 +86,7 @@ volumes: networks: # Not `internal: true`: headless Chrome still needs outbound access to reach - # kagane.to. Isolation here comes from membership (only manga-api and + # kagane.to. Isolation here comes from membership (only bookmark-api and # headless-shell join it), not from cutting egress. browser: ipam: diff --git a/docs/design-system.md b/docs/design-system.md index 468b5b0..9f4aeb7 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -1,6 +1,6 @@ -# Cinder — mangaBookmark design system +# Cinder — BookmarkManager design system -Source of truth: the Claude Design project **mangaBookmark Web UI** +Source of truth: the Claude Design project **BookmarkManager Web UI** (`969ac210-fe02-4c01-ae1b-9a271dcc779a`, `index.html` + siblings `archived.html`/`fav.html`/`finished.html`/`new.html`/`login.html`/`mobile.html`, `style.css`, `filter.js`). This file records the rules that got implemented so @@ -118,7 +118,7 @@ root is at the mercy of the host site's CSP. Recurring specs (copy these rather than inventing sizes): - Brand: `400 26px/1 display` (`30px` ≥720px), inline SVG mark (§4) + `` in - ember italic — `mangaBookmark`. + ember italic — `BookmarkManager`. - Row title: `400 21px/1.2 display` (`22px` ≥720px). - Tab: `400 17px display` (`18px` ≥720px), active gets `border-bottom: 2px` in `--paper` (`--ember` for Updated) plus `margin-bottom: -1px` so it lands on diff --git a/userscript/CLAUDE.md b/userscript/CLAUDE.md index 9f1c225..5c22a98 100644 --- a/userscript/CLAUDE.md +++ b/userscript/CLAUDE.md @@ -3,10 +3,10 @@ Guidance for Claude Code working under `userscript/`. See root `CLAUDE.md` for t ### Userscript structure (single IIFE, `manga-bookmark.user.js`) 1. **Site adapters** — one per host, `detect(location, document)` return page `type` + IDs. Identify type/IDs from **URL regex** (most stable); pull `title`/`cover` from **`og:title`/`og:image` meta tags**, not CSS classes. -2. **API client** — `apiGet/apiPut/apiDelete` with bearer header; `localStorage` key `mangabm:cache` for instant render + offline fallback. +2. **API client** — `apiGet/apiPut/apiDelete` with bearer header; `localStorage` key `bmgr:manga:cache` for instant render + offline fallback. 3. **Progress logic** — auto-upsert `last_chapter` only when `chapterNum >= stored last_chapter_num` (re-reading old chapters must not regress progress; unparseable -> set current). Manual panel override forces any value. 4. **Retry queue** — every write go through `pushBookmark`/`pushDelete`, so - failed mutation park in `localStorage` (`mangabm:queue`) and replayed on + failed mutation park in `localStorage` (`bmgr:manga:queue`) and replayed on next navigation, reconnect, or `refresh()`. 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