iamnbutler/shelfgoblinPublic

Shelf Goblin โ€” discover LitRPG audiobooks, follow series, and track your library.

History

README.md

๐ŸชŽ Shelf Goblin

An audiobook catalog for LitRPG and progression fantasy. Follow series, track the books you have read, find similar series, and check audio releases. Forked from LitRPG Chart.

Open Shelf Goblin ยท Catalog inspector

Run the app

Use Node 22.9+ and npm. A fresh checkout runs from committed public JSON without a catalog database, API keys, or the private producer repository.

npm ci
npm run dev
npm run check
npm run check:worker
npm test
npm run build

npm ci builds the shared catalog contract before synchronizing SvelteKit. After changing contract source, run npm run build:contract before checking consumers.

The app opens into a compact cover grid with search, filters, series details, and release views. My library contains followed series; read state belongs to books, with edition aliases preserving older saved IDs. Future releases and unresolved audio dates are not silently marked read. GitHub sign-in synchronizes a library across devices; browsing and a guest library also work without an account.

App and catalog ownership

RepositoryOwns
shelfgoblin โ€” publicUI, reader accounts and libraries, account Worker, public JSON, shared catalog contract
shelfgoblin-data โ€” privateAcquisition, SQLite and migrations, source evidence, OpenAI/Jev enrichment, quality research, benchmarks, exports, backups

The app reads static/data/catalog.json; the inspector reads static/data/health.json. These are replaceable public snapshots, not the durable catalog. Builds and page loads never scrape sources or call models. The inspector scores data completeness and evidence quality, not literary quality. Experimental book, series, and author rankings remain in the producer until calibrated and explicitly enabled for readers.

Full source pages, review bodies, reviewer identifiers, model receipts, private reports, and cached assets stay in the private producer and its archive. Public exports carry bibliographic facts, cover links, reviewed descriptions, classifications, and derived reader context. Keep acquisition and model credentials out of browser code and committed files. Pipeline commands and restore instructions now live in the producer documentation.

Shared catalog contract

packages/catalog-contract is the single implementation of public catalog types and pure identity, edition, series, audio-coverage, and recommendation behavior. The app uses it as a workspace; src/lib retains compatibility re-exports. The producer installs a versioned public GitHub release archive with an exact URL and lockfile integrity.

npm run build:contract
npm test
npm pack --workspace @shelfgoblin/catalog-contract

To release a contract change, bump its version, build and test it, attach the package archive to a new immutable public release, then update the producer's dependency and lockfile together. Never replace a released archive or copy the source into the producer. App builds use the local workspace and do not fetch a contract release.

Recurring catalog work

The public repository's catalog-refresh.yml runs private producer code on standard public-repository runners, avoiding private-repository Actions minutes. It schedules a bounded refresh at 08:17 UTC daily. Both the workflow and worker skip 22:00โ€“02:00 UTC, including delayed and manual starts, and reserve time for a private checkpoint before 22:00.

CATALOG_DATA_TOKEN is provisioned as an encrypted secret in this public repository: a dedicated fine-grained token scoped to Contents read/write for only iamnbutler/shelfgoblin-data, expiring December 18, 2026. Rotate it before expiry. The workflow's GITHUB_TOKEN writes only to this public repository. Paid refreshes use the configured encrypted OPENAI_API_KEY and TYPESAFE_API_KEY secrets. The verification-only job reserves its 15-minute timeout; full refreshes reserve 27 minutes for work and checkpointing.

Manual dispatch defaults to task=verify, which installs, type-checks, and tests the private producer without model credentials. Use task=refresh for catalog work; its manual default no_enrich=true skips paid inference. The scheduled refresh enables the configured bounded enrichment stages.

A refresh restores and verifies the private snapshot, processes selected work, and saves a private checkpoint before committing allowed public JSON. Private child logs stay on the ephemeral runner; raw evidence and test output are never uploaded as public artifacts. Checkpoint failure prevents public publication. Runner loss can still interrupt a checkpoint, so inspect a failed run before retrying paid work. Neither Cloudflare nor GitHub Pages deploys automatically after a catalog commit.

Accounts and deployment

Cloudflare serves https://shelfgobl.in/. The account Worker exchanges GitHub OAuth codes server-side using state and PKCE; D1 stores account libraries and hashed session tokens. Library revisions prevent stale writes from silently replacing newer changes. Guests can explicitly add their local library to an account, or use export/import. The optional adult-content setting uses a self-attested date of birth; GitHub sign-in does not verify age.

Production resources retain their compatibility names: Worker litrpg-hub, D1 binding DB, and database litrpg-hub-accounts. Browser storage keys and older library export formats also remain unchanged. wrangler.jsonc records the production origin and existing database ID.

npm run db:migrate          # Apply reviewed account migrations before deployment
npm run secrets:cloudflare # Upload only the two OAuth credentials from .env
npm run deploy:cloudflare  # Build at / and deploy the app + account Worker

Register https://shelfgobl.in/auth/callback/ in the GitHub OAuth app and keep SITE_URL / OAUTH_GITHUB_REDIRECT_URI aligned. Deployment is manual. The Pages workflow is also manual; a repository rename changes its project path. The BASE_PATH=/litrpg-hub CI build tests the historical subpath only and does not promise that the old Pages URL remains hosted.

The unchanged Worker ID preserves the former Workers address for compatibility. Browser storage is origin-specific: unsynced old-domain libraries need exporting there and importing at Shelf Goblin. Synced account libraries remain attached to the same GitHub identity. Signing in does not automatically combine guest data with account data.

For local accounts, use a separate development OAuth app with callback http://localhost:5173/auth/callback/. Put its two credentials in ignored .dev.vars.local, run npm run db:migrate:local, then start npm run dev:accounts and npm run dev in separate terminals. Vite proxies account requests to Wrangler; local D1 state stays under .wrangler/. Browsing needs neither service nor keys.