Each user gets an own read-ahead queue, served in turn, so two people reading
at once no longer cancel each other's chapter. The report rounds phase times
and measures the quiet time before a slow storage probe correctly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A switchable trace (DATA/perf/trace.on or PERF_TRACE=1) records every request's
phases (stat, open, first read, disk vs network wait), browser resource timings,
page-turn waits and flow stalls, a cold storage probe, event-loop lag, scans and
scraper calls as JSONL. tools/perf-report.js says which phase slow loads spend
their time in; tools/perf-bench.js simulates readers.
READ_AHEAD (default on) reads an opened chapter's pages, then the next chapter's,
into the OS cache so a NAS's cold reads stop reaching the browser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page hotlinked ~100 AniList CDN covers per load and some failed at random.
The reader now downloads each catalog cover once (sequential, ~1/s, stops on
401/403/429, images up to 2 MB) into data/greatest-covers/ and serves it from
/api/greatest-cover with a year-long browser cache; until a copy exists the
route redirects to the catalog URL. The page also accepts the server's
relative cover URLs, so owned titles show their library cover again.
GREATEST_COVER_WARM=0 turns the downloads off (tests, offline installs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Cache prune counts directory entries first and only reads files when over
the cap; tests for cross-kind prune, schema-mismatch and corrupt entries.
- Scoring tolerates malformed manga.json and cached records (non-list tags,
object or null relations, non-object items); a tag without a rank no longer
passes the top-tag filter; authors without an integer AniList id are dropped.
- discovery.json publishes only candidates some feed shows; examined/clipped
still describe the pool. Tests that asserted the published count equals the
pool size now check the pool via candidatePool() and the published set
against the feeds.
- The post-harvest rescore failure test now breaks discovery.json right before
the publish (via a scraper stand-in) instead of relying on tags: 5, and a
following successful harvest clears the stale lastError.
- Consent text names everything sent to AniList; paused label mentions admin
harvests; the UI test asserts the XSS seed instead of skipping it.
- Slot-mix test proves the fresh/outside reservations matter.
- Docs: privacy note, fresh fallback reason, per-period cap, dry-run wording,
published-candidate rule; plan/spec hygiene and spec status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- scraper/metadata/release-dates.js fills x-dates.release from MangaUpdates
publisher releases. MangaUpdates also lists English back-catalog re-releases,
so a date is kept only inside the AniList original run, when no scanlation or
source upload is more than 4 days earlier, and when it fits the release order
(no lower chapter dated after a higher one). Seasons that restart chapter
numbers are skipped; dates this job wrote and no longer accepts are withdrawn.
- The scraper refreshes ongoing series nightly before its update checks, only
while no download runs, and bumps the publish revision so the reader reconciles.
- tools/release-dates.js for a whole library (dry run default). Production dry
run: 728 chapters dated (Kagurabachi, JJK, Chainsaw Man, One Piece from 928).
- Peer sync: chapters this library already has adopt a peer's earlier ecosystem
date (job result ecosystemLowered); the peer listing now carries dates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Chapter rows show when you first finished a chapter (setting: date + hour,
date, off) from a new durable progress doneAt; older reads fall back to the
reading clock's earliest remembered completion.
- chapter.json keeps added (arrival in this library, now kept on replace) plus
x-dates.source (source upload, bulk imports flagged), x-dates.release
(official, for a later metadata job) and x-dates.ecosystem (first entry into
any Manga Circus library; carried over peering, earliest wins, clamped).
- Rows show one release date by tier: official > source upload > ecosystem >
library, with all known dates in the tooltip (setting to hide).
- Fixes: WeebCentral dates came from the chapter page, which has none (every
chapter got the download time); they now come from the full chapter list.
MangaDex no longer writes a group's publishAt into added. Peering sends dates.
- tools/backfill-chapter-dates.js migrates an existing library (dry run default).
- Coverage dry run on the server: docs/chapter-dates-coverage-2026-10-24.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stall watchdog now runs inside the scraper (ScraperService.checkStalls,
every 30 s from the work loop): same rule as before, one checkpoint-safe
restart, quarantine on the second stall, missing checkpoint is no evidence,
and paused time no longer counts. Stalls are recorded as scraper incidents
(failure.kind 'stall'). The private /api/recovery endpoint is gone.
Dropped with the service: reader/scraper-unreachable incidents (supervisor
and Docker handle crashes; the health page shows "Scraper: unreachable"),
duplicate job-failure incidents and the unused /metrics endpoint.
The stack is now reader + scraper (+ optional peer-gateway).
The supervisor test moved to runtime/test (npm run test:runtime stays).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reader owns a scheduled backup into BACKUP (Compose: BACKUP_PATH mounted at
/backup, off when unset). A run pauses the scraper queue through its control
API, waits (bounded) until no job is active, writes a verified tar.gz of DATA
with db.json from memory and scraper/queue.json stored paused, excludes
runtime/lock/pid/temp files, resumes only a queue it paused, then applies
count- or day-based retention to its own archive names only. Schedule,
retention, "Back up now" and last result live in Settings > Service health.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zero-dependency Node server + plain HTML/JS frontend.
- Library is a folder tree (Manga/Book/Chapter/pages), indexed in place;
page roles (cover, double, extra, rotation) are file name tags
- Accounts with per-user progress, reading settings, follows and
new-chapter notifications (in-app + optional webhook)
- Reader with Flow, Page and Book modes, pointer-anchored page zoom,
tap zones, auto-hiding controls, chapter picker, page preloading
- Multiple versions per chapter with community votes
- Docker image and compose file
- Demo library generator (tools/make-dummy.js)
- Docs: architecture, library format, import, scraper guide