iamnbutler/shelfgoblinPublic

Shelf Goblin — discover LitRPG audiobooks, follow series, and track your library.

Add an 18+ age gate and plain-language reader copy

Two lanes that are entangled in +page.svelte and library.ts, so they
land together rather than as contorted partial commits.

Age gate. Sexualized and explicit titles are hidden for everyone by
default, signed in or not, and can only be revealed by a reader who has
confirmed a date of birth showing 18+ and has then opted in. GitHub's
OAuth API exposes no date of birth, age, or age bracket in any scope, so
the claim is self-attested; this is an honest record of what the reader
told us, not age assurance.

The unlock is never a trusted stored boolean. It is recomputed from the
date in parseAdultConsent, in the Svelte derived state, and again
server-side before every write, so a request claiming allowAdult on a
minor's date is persisted as a refusal. The date itself is stored rather
than a derived flag so a reader who confirms at 17 gains the option on
their birthday without being asked again. isAdult computes the 18th
birthday as a calendar date rather than elapsed days, so leap days and
time zones cannot move it.

Only sexualized and explicit sit behind the gate. Harem is a story
structure and AI disclosure is a production fact, so both stay ordinary
preferences. Unknown signals still show: forcing them out would empty
the grid at current cover-assessment coverage.

Every consumer reads effectiveFilters rather than raw filters, so a
stale localStorage value, another tab, or an imported settings blob
cannot reveal anything on its own. Gated controls are not rendered while
locked and setFilter refuses gated keys as a second line of defence.
Attestation lives in D1 for signed-in readers and localStorage for
guests, and a guest's claim is carried up on sign-in rather than asked
for twice.

Reader copy. Internal pipeline and rubric vocabulary was surfacing in
the UI. Filter labels, catalog-completeness notes, series and book
drawer notes, and the Jev and cover assessment note strings now read as
plain language for readers.

Also fixes workers/accounts.test.ts, which only ever executed migration
0001. It now applies every migration in sorted order with named-column
seed inserts, so the next ALTER TABLE does not silently break it.
IAiamnbutler authored
def4d86Patch
Parentf261171
21 files changed+533-77
StackedSplit
scripts/backend/covers/content.ts+1 −1
scripts/backend/jev/assessment.ts+1 −1
src/lib/account-sync.ts+41 −5
src/lib/adult.test.ts+56 −0
src/lib/adult.ts+72 −0
src/lib/components/BookDetail.svelte+12 −4
src/lib/components/BookTile.svelte+7 −1
src/lib/components/ReaderImpressions.svelte+11 −10
src/lib/components/SeriesDetail.svelte+3 −3
src/lib/components/SeriesTile.svelte+1 −1
src/lib/components/readerimpressions.test.ts+4 −4
src/lib/components/render.test.ts+9 −9
src/lib/content-notes.test.ts+74 −0
src/lib/content-notes.ts+55 −0
src/lib/library.test.ts+11 −11
src/lib/library.ts+8 −6
src/lib/styles.css+4 −0
src/routes/+page.svelte+99 −14
workers/accounts.test.ts+35 −3
workers/accounts.ts+22 −4
workers/migrations/0002_adult_consent.sql+7 −0