Consolidate MangaCircus app, scraper, and Compose baseline

This commit is contained in:
Maashkinoozhe committed 2026-10-07 23:28:20 +02:00
1 parent cfb2d79103
commit eef4ffa07c
103 files changed
+9316 -172

No files matched your search

+141
View File
@@ -0,0 +1,141 @@
# Collections and relationships
Collections are a presentation layer, not a filesystem migration. Creating, reordering,
or removing one never renames manga/chapter folders or changes reading IDs.
## Using the UI
Open **Settings → Admin → Collections & metadata** (or **Manage collections** on the dashboard).
Create a collection, add library manga, choose each member's role and reading order,
and save. Choose a member cover, a mosaic of up to four covers, or upload a custom
image under 600 KB. Dashboard display can be grouped, expanded, or individual; the
choice is remembered per browser. A collection page shows aggregate progress and
continues the first unfinished member in the chosen order.
**New collection** starts an unsaved draft using an ungrouped library manga. Rename
it, add members and choose **Save collection** (or **Save all changes**). Each manga
can belong to one collection. If all manga are grouped, remove a member from another
collection first; a collection's final member is retained until you delete the
collection. Deletion never removes manga or reading progress.
Save feedback and errors appear above the editor. Controls are locked during a save;
failed saves retain drafts. If another admin has changed the catalog, the stale write
is rejected. **Reload catalog** fetches the latest revision, with confirmation before
discarding edits; copy any wanted changes first. Unsaved JSON survives collection
selection and must use **Validate & save JSON**, not an ordinary collection save.
Metadata imports/research are blocked while drafts exist so they cannot silently
replace them. Relationship edits save immediately when there is no collection draft.
Creation works on HTTPS, localhost and ordinary HTTP LAN/Tailscale addresses. The
browser uses secure random bytes when its secure-context-only `randomUUID` API is
unavailable. This does not change networking or the server's revision/validation rules.
Tag combinations match a single member, not tags scattered across unrelated members.
Reading-time totals only include the current account's visible Reading Story. Collection
publication status is ongoing if any member is ongoing, completed if all are completed,
otherwise mixed. Collections do not control scraping or merge editions.
## Three distinct concepts
| Concept | Meaning | Owner |
|---|---|---|
| Relationship | Directed fact: target is a prequel/sequel/adaptation/etc. of source | Reader's Catalog Curator |
| Collection | Which library manga share a dashboard tile | Admin decision |
| Reading order | Recommended order within that collection | Admin decision, independent of chronology |
One manga belongs to at most one collection, preventing duplicate dashboard tiles and
double-counted progress. Relationships can connect any number of works across groups.
Only confirmed relationships appear on reader manga pages. Suggestions, rejections
and conflicts remain visible to the admin. Removing a collection leaves its manga,
reading history and relationships intact.
## Metadata and agent responsibilities
The metadata provider, **not the image scraper**, supplies relationships. AniList
metadata now retains manga relationship edges in `manga.json` → `x-anilist.relations`.
Existing metadata will not magically gain these fields: use **Research relationships**
for a manga with a verified AniList ID, or import already-cached suggestions.
Research uses one public metadata lookup with a 30-day cache, never image-host traffic.
Only relationships whose two exact AniList identities already exist in the library
become suggestions. This does not auto-enqueue missing manga or guess title matches.
An agent is a **catalog researcher/curator assistant**. It can read the admin JSON export,
research public evidence, identify conflicting claims and propose relationships or
membership edits. It should preserve rejected decisions and report uncertainty.
It must not turn a shared-universe link into an automatic collection or an unquestioned
reading order. No embedded LLM, API credentials, automatic research loop, or autonomous
catalog approval is enabled in this release. That optional capability belongs behind
this same evidence/review boundary, not inside adapters or Sentinel.
## Persistence and API contract
`data/catalog.json` is reader-owned, version 1, atomically replaced on save. Its
revision supports optimistic concurrency; stale writes return 409. Unknown members,
duplicate membership, invalid relationships and cover paths are rejected. Existing
members absent from a temporary mount are retained rather than erased.
Custom covers are content-addressed in `data/collection-covers/`. Include both in backups.
Authenticated reader APIs:
- `GET /api/collections`, `GET /api/collection?id=…` — account-specific public views.
- `GET /api/admin/catalog` — admin export plus available manga, roles and relation types.
- `POST /api/admin/catalog` — validated full replacement, with current `revision`.
- `POST /api/admin/catalog/suggest` — import cached edges, with current revision.
- `POST /api/admin/catalog/research` — `{m: libraryMangaId, revision}`; verified metadata lookup.
- `POST /api/admin/collection-cover` — `{id, revision, base64}`; validated image upload.
Example proposal (use real IDs and the latest revision from the export):
```json
{
"version": 1,
"revision": 0,
"collections": [{
"id": "moon-garden",
"title": "Moon Garden Universe",
"description": "Main story, then the prequel.",
"grouped": true,
"coverMode": "mosaic",
"coverMangaId": "Moon Garden",
"members": [
{"mangaId": "Moon Garden", "role": "main", "readingOrder": 1},
{"mangaId": "Before the Rain", "role": "prequel", "readingOrder": 2}
]
}],
"relations": [{
"from": "Moon Garden", "to": "Before the Rain", "type": "prequel",
"state": "suggested", "method": "agent-research", "confidence": 0.8,
"explanation": "Verify the publisher's chronology before accepting.",
"evidence": [{"provider": "Publisher", "url": "https://example.com/work", "note": "Chronology"}]
}]
}
```
Provider contract: [AniList relationship connections](https://docs.anilist.co/guide/graphql/connections).
## Repeatable functional acceptance
Run `npm.cmd test`, build `docker compose build reader`, then:
```powershell
# Playwright is optional developer tooling; not a deployed application dependency.
# Set NODE_PATH to its installed package root if it is not on normal resolution paths.
node tools/test-compose.js --collections-ui --settings-ui
```
The runner creates an isolated Compose project on loopback 8180 with three synthetic
manga, disposable accounts, paused scraper and disabled discovery. It never mounts
the real library, DB or queue. Headless Edge (override `QA_BROWSER_CHANNEL` if needed)
proxies a synthetic `http://manga-qa.test:8180` origin to that fixture, reproducing the
browser restrictions of a non-localhost HTTP address without using a real tailnet
account. Screenshots: `data/qa/collections/desktop-editor.png` and `mobile-editor.png`.
Coverage: creation, validation, persistence after reload, member add/remove/role/order,
selected/mosaic/custom covers and upload limits, JSON round-trip/draft protection,
relationship creation/rejection/duplicates/removal, cached import, missing verified
metadata identity, desktop/mobile edits, dashboard grouping and collection reading
order, delete/cancel, overlapping saves, transient save failure/retry, concurrent-admin
revision conflicts, and ordinary-reader read/write denial. No external manga or
metadata research is performed; provider lookup success remains a separate test.
The earlier settings test checked collections navigation only; this suite exercises
the actual collection workflow and the insecure-origin failure it missed.