Files
mangaBookmark/userscript/AGENTS.md
sulthan 766aa8f00d docs: make every AGENTS.md cite code, not docs or issues (#113)
Every `AGENTS.md` now cites code and nothing else.

## Why

Two rot mechanisms, same symptom — an agent confidently follows a stale statement:

1. **Non-code citations.** A spec, ADR, plan file, or issue records what was true when it was written. Nothing updates it when the decision reverses.
2. **Prose restating mechanism.** The code changes, the paragraph doesn't, and the next reader trusts the paragraph.

Code is the only source true at read time.

## What changed

**All three files:** removed every ADR ref, spec/plan pointer (`docs/superpowers/specs/*`, `plans/*`, `docs/research/*`), `DEPLOY.md`/`REDEPLOY.md`, `docs/agents/*`, and issue number. Facts those links carried are restated inline — the `tea` command set and the five triage label strings now live in the root Forge section. `### Domain docs` is deleted: it pointed only at `CONTEXT.md` and `docs/adr/`, neither of which exists.

**`backend/` and `userscript/`:** rewritten around derivability.

| Class | In code? | Treatment |
|---|---|---|
| Structure — packages, routes, env vars, columns | yes | name the symbol, nothing else |
| Mechanism — what a function does | yes | symbol + one line |
| Rationale — why, what a "simplify" breaks | **no** | written out |
| Measurement — observation against a service we don't control | **no** | written out, dated |

`backend/AGENTS.md` 20578 → 15512 bytes, `userscript/AGENTS.md` 7129 → 5912. Root grows 16905 → 19292: the cost of inlining the `docs/agents/*` facts plus the new rule.

**Rule** recorded in root as `## Writing an AGENTS.md`. Sole non-code exception is a sibling `AGENTS.md`. Closing clause: every symbol named must exist, since a dead pointer is a bug rather than a stale sentence.

**Harness-agnostic:** dropped the `Guidance for OpenCode (and Claude Code)` openers for plain scope lines.

## Verification

Applied the new rule to itself — extracted all 118 backticked identifiers across the three files and checked each against every `.go`, `.js`, `.sql`, `.html` and `.css` source. Zero repo symbols missing; the 8 non-matches are external (`GM_setValue`, `navigator.webdriver`, `HeadlessChrome`, `curl`, …).

That check caught a claim that was **already lying** on `main`: the cover section said `CoverFetcher` was gone, but `NewCoverFetcher`, `TLSCoverFetcher` and `BrowserCoverFetcher` are all live in `internal/latest`. Now names only the genuinely dead `/img/kagane/{id}` route. Exactly the failure the rule exists to prevent.

No code touched — documentation only, nothing to test.

Reviewed-on: #113
Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
2026-08-17 13:43:49 +07:00

5.7 KiB

Scope: userscript/.

Each entry names the code that holds the truth. The prose is only what the code cannot tell you: rationale, invariants a refactor would break, and dated observations about sites we don't control.

Structure — single IIFE, manga-bookmark.user.js

Six parts, in file order: site adapters, API client, progress logic, retry queue, UI, SPA navigation.

Site adapters — one per host, detect(location, document) returning page type + IDs.

  • Identify type and IDs from URL regex, which is the most stable surface a site exposes; take title from og:title (or the page heading where a site ships no og: tags), never CSS classes.
  • No adapter reads a cover. The backend acquires, stores and serves every Cover from its own origin, the wire cover is already an address there, and apiPut strips any cover off an outgoing body.

Progress logic — auto-upsert last_chapter only when chapterNum >= stored last_chapter_num; unparseable sets the current value. Re-reading an old chapter must not regress progress. A manual panel override forces any value.

Retry queue — every write goes through pushBookmark/pushDelete.

  • Entries are markers ({key, op, sendStatus, attempts}), never payloads: the body is read from cache at send time, so one entry per key gives ordering and coalescing for free.
  • sendStatus is sticky — while an archive is pending, later writes to that key keep carrying the bucket. Without it a successful in-between write silently un-archives the series.
  • refresh() drains before it fetches and overlays anything still pending, so the list never flaps.
  • 400 drops the entry, 401 aborts the pass and keeps the queue, transient failures retry to a cap. Latest-chapter writes deliberately stay out of the queue.

UI — rendered inside a Shadow DOM root to isolate it from site CSS, which is critical on mobile.

  • The FAB's hit area is widened by an invisible #hit child. #fab must keep touch-action: none and must not regain overflow: hidden.
  • touch-action is resolved at gesture start, so the strip cannot be both browser-scrolled and script-dragged. makeDraggable therefore splits by intent: a swipe from #hit scrolls via window.scrollBy, a hold of ARM_MS arms a reposition drag, and the visible sliver drags with no hold.

SPA navigation — Asura is Astro and client-routes on comic/chapter pages, so history.pushState/replaceState are patched and popstate listened to, and detect() re-runs on URL change. Demonic uses classic reloads, where the initial document-idle run suffices.

Live URL shapes

Encoded in the adapters; the notes below are the parts a reader of the regex would get wrong. Verified 2026-07-26 unless dated otherwise — sites drift, so re-check against a live page before trusting any of it.

  • asurascans.com — the series slug carries a site-wide build-hash suffix (e.g. -059befe1) that rotates on every redeploy, so seriesId must strip it (stripBuildHash here, asuraBuildHash in the backend) while URLs keep the full slug — stale-hash URLs 302 to current ones.
  • demonicscans.org — slugs may URL-encode punctuation, and the older chaptered.php?manga=<id>&chapter=<n> form still exists as a redirect, which is what series-page chapter-list anchors link through. Encodings (including triple-encoded punctuation like %25252D) are identical on /manga/ and /title/ pages, so decode-once seriesIds match (verified 2026-07-28).
  • comix.to — only the leading <id> is identity; the slug re-renders when a series is renamed (comixSeriesId). It is an SPA that never rewrites og:title: the server-rendered head keeps whatever document loaded first, so on a cold load og:title is the homepage's name and after an in-page hop it is the previous series'. document.title is the one thing client routing updates, hence titles come from there with the chapter page's " · Ch.<n>" tail stripped. It publishes no og:image either, one of the reasons cover acquisition moved to the backend.
  • kagane.to — reader URLs carry no chapter number, so the number comes out of og:title. Two shapes exist, "<Series> - Chapter <n>[ - Episode <n>]" and "<Series> - Volume <v> Chapter <n>"; both must yield a bare series title, or the volume tail lands in the bookmark's title. Its covers are challenge- and CORP-protected, so nothing outside kagane.to can load one — the panel renders the backend's cover address like every other Site.
  • novelfull.com (novel script) — no og:* tags at all, so the title comes from h3.title (series) or a.truyen-title (chapter).
  • lightnovelworld.net (novel script) — chapter paths are flat at the site root and their slug is a Chapter Slug, not an identity: a Series may publish under several. The Series address is read off the page's a[aria-label='All Chapter'] (fallback: the BreadcrumbList's second crumb), and a chapter page with no pointer resolves to other so no Bookmark is offered. The client runs no latest-chapter scan for this Site — computeLatestChapter yields null and backgroundRefreshLatest skips it before any fetch — because the backend Poll's one-hour cooldown dominates the client's four-hour throttle, so a scan would add no freshness while having to truncate at the page's wpdiscuz thread, a public write surface.

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, because this script has no previous installation to carry keys over from. Installed alongside the manga script; both write to the same backend, discriminated by LIBRARY.