01-projects/printables-product

Scribble Works — fresh-eyes onboarding review (codex)

2026-09-05·review·status: draft

Founder-requested full onboarding review by codex (gpt-5.6-sol, high effort), ahead of deferring more work to external models. Read-only: cloned the repo, read ARCHITECTURE.md, all migrations, core src/lib and functions/api code, the last 30 commits, and the full main...release diff. Ran npm ci + the full test suite (PP_LIBRARY pointed at an empty dir to avoid the founder's private vault auto-discovery) — 20/20 gates PASS, 31/31 live PDFs reachable.

Headline: recommends holding release before merging to main on four P0 findings. Ray independently verified P0-2 (removed-adult reauthentication) directly in src/lib/accounts/store.js and src/lib/auth/store.js — confirmed real. The other three (migration cross-contamination, privacy-copy mismatch, cross-household local-storage leak) are codex's findings, not yet independently re-verified line-by-line by Ray.

Scribble Works: fresh-eyes engineering review

Review date: 2026-09-05
Repository: RayDataCo/scribble-works
Production branch reviewed: main at 4673983
Shipping-candidate branch reviewed: release at 769b9a0
Review mode: read-only source, history, configuration, tests, and public dependencies. No Cloudflare dashboard, D1, R2, Resend, Twilio, or Gateway account access was available.

Executive summary

Scribble Works is a thoughtfully constrained, mostly static Astro application with a strong culture of executable checks. The code is unusually good at documenting why an operational rule exists, and the project has solid security instincts around magic links, model-output validation, generative kill switches, security headers, Twilio signature verification, and minimizing analytics data. The application is understandable without a large framework, and the core browse/download experience has a small runtime failure surface.

I would nevertheless hold the current release branch until four issues are resolved or explicitly risk-accepted:

  1. Both D1 databases point at one mixed migration directory. The documented wrangler d1 migrations apply commands can apply account migrations to the UGC database and UGC migrations to the accounts database. The migration files themselves repeatedly say that this must never happen.
  2. A removed household adult can regain access. On release, removing an adult deletes their home-adult mapping but leaves the login identity. A later magic-link sign-in trusts the identity's old household, and the actor resolver can fall back to another lead. This appears capable of restoring privileged household access to a removed user.
  3. The public privacy promises do not match production data handling. The live-facing copy says there are no child profiles and that a child's name is never stored. The product stores child profiles including full birth date, and account tray synchronization can store a child's name inside serialized customized HTML. The daily-email Worker also sends child names and personalized attachments through Resend. Consent tables exist, but the relevant runtime flows do not use them.
  4. Household switching can copy one household's tray into another. The browser uses one global local-storage key. If a user switches from household A to household B and B has no server state yet, the sign-in synchronizer uploads A's remaining local tray to B.

The first issue is already present on main; the privacy and retention concerns are also relevant to main. The removed-adult privilege issue and cross-household copy path are introduced or made reachable by the multi-household work on release.

Beyond those blockers, the largest engineering risks are non-atomic multi-statement account workflows, unenforced or unverifiable retention promises, daily-email duplicate/no-retry behavior, environment-dependent builds, and documentation that has fallen behind the current architecture. These are tractable, but they deserve an explicit stabilization pass before more account features are layered on.

What I reviewed

I followed the repository's onboarding path and then traced the important user and operator flows:

I did not mutate application source, push, open a PR, or inspect live infrastructure. Deployment-state statements below are therefore phrased as repository-declared status or questions to verify, not assertions about the Cloudflare account.

System mental model

Runtime topology

Surface Runtime and role Durable dependencies
Public site Astro static pages on Cloudflare Pages Public R2 PDF origin; build-time game metadata/assets
Pages Functions /api/* account, auth, customization, downloads, feedback/contact, and Shopper endpoints Accounts D1, UGC D1, KV namespaces, AI Gateway/OpenAI, private R2, Resend, Turnstile
Daily-email Worker Scheduled standalone Worker; packages personalized playsets and sends one household email Accounts D1, public R2 PDFs, site-hosted fonts/previews, Resend
Feedback intake Worker Twilio webhook for incoming SMS/MMS UGC D1, private R2, Twilio signature validation
Retention Worker Scheduled deletion of selected expired rows/objects UGC D1, Accounts D1, private R2
Build pipeline Astro build plus content, policy, auth, database, Worker, PDF, and live-R2 gates Optional founder vault, public R2, Chromium, Poppler depending on task

The static site is the right default for this product. Pages Functions are narrow additions rather than a server-rendered application, which keeps public browsing resilient. The tradeoff is that account behavior is spread among browser scripts, Pages Functions, shared libraries, D1 schema conventions, and standalone Workers; changing a data contract requires checking all five places.

Data ownership and relationships

There are two intentionally isolated D1 databases.

UGC D1 contains:

Accounts D1 contains:

The release data model has three concepts that are easy to conflate:

  1. An identity is an email login and has an immutable home household.
  2. An adult is a role-bearing row within a household.
  3. A session has a current household, while session_adults stamps the adult actor for that household.

Membership in additional households is inferred by matching the identity email to adults.email; there is no explicit identity-to-membership join table for non-home households. This makes email normalization and immutability part of the authorization model. Any future email-change feature will need a deliberate migration rather than a simple column update.

The accounts schema generally avoids foreign keys in newer account tables so retention/deletion can be staged. That is a valid design choice only if store operations and sweepers rigorously maintain referential integrity. At present, several do not do so atomically.

Core user flows

Browse, download, customize, and print

  1. At build time, src/lib/games.js reads game metadata and keeps only status: live games for public routes.
  2. /browse and /game/[slug] are static pages. Game PDFs are linked directly to the public R2 origin rather than proxied through Pages.
  3. A download click sends a best-effort beacon to /api/downloads; only aggregate KV counters are kept. The counter is intentionally lossy and forgeable and should not be treated as billing-grade analytics.
  4. The browser tray lives under the single local-storage key sw.playset.v1 and has six slots.
  5. An all-original playset lazy-loads the PDF packager, fetches six R2 PDFs, and produces a seven-page PDF client-side.
  6. A customized playset cannot be merged into the original PDFs. It instead prints a browser-composed document using stored serialized HTML in srcdoc iframes.
  7. Once signed in, browser state is synchronized wholesale to one D1 household_state row.

Important operational implications:

Daily personalized playsets

The release Worker is scheduled hourly at minute 30. For each eligible Plus household at the configured local delivery time it:

  1. loads up to eight eligible child profiles and the previous 14 days of delivery history;
  2. deterministically curates games per child;
  3. fetches and packages six PDFs plus a cover into one PDF per child;
  4. sends a single email to households.email with every child's PDF attached; and
  5. inserts a daily-delivery result row used as a same-day deduplication marker.

This flow reads child names, full birth dates, interests, and languages. Names and personalized content appear in generated documents passed to Resend. Additional household adults do not receive the message; the household contact email is the sole recipient.

The repository labels this Worker as written and tested but not deployed, and its feature flag defaults off. That status must be checked in Cloudflare before treating it as current production fact.

Branch and release model

main is production. release is a long-lived shipping candidate; feature branches branch from and target release, and the founder ships by merging release to main. At review time, release was six commits ahead with no divergence and changed 54 files (+4,062/-368). The six release commits add or refine adult invitations, multi-household membership/switching, daily-email packaging v2, print-slot layout, and account/privacy copy.

This model can work for founder-controlled releases, but a long-lived candidate makes two controls especially important:

The current CI triggers on pull requests and pushes to main; a direct push to release is not independently covered.

Hard conventions a new engineer must know

These are not style preferences. Violating them can break production or silently corrupt isolation.

  1. Feature PRs target release; only the founder ships release to main. Start from fresh release, and inspect the aggregate release diff before shipping.
  2. D1 migrations are forward-only and idempotent. Newer migrations use side/join tables instead of ALTER TABLE, because SQLite/D1 does not support the desired ADD COLUMN IF NOT EXISTS workflow.
  3. Accounts and UGC migrations must never cross databases. The current directory layout does not enforce this; see Finding P0-1.
  4. The top-level Pages environment is preview; [env.production] is production. Reversing these bindings points preview code at the wrong accounts database.
  5. Preview deliberately shares the production UGC D1 and KV resources while using a separate accounts D1. Preview customizations, counters, generated art, and feedback tests can therefore affect production-like UGC data and quotas.
  6. Pages configuration is file-driven. A binding added only in the dashboard can disappear on the next deployment.
  7. Do not add an R2 binding to the Pages project. A recorded 2026-09-01 deployment failure is why Pages uses public/private HTTP or Worker paths instead.
  8. PUBLIC_TURNSTILE_SITE_KEY is a build-time value. Runtime-only configuration is insufficient.
  9. Production PDFs and generated game artifacts are not committed. The prebuild stages or generates them; the completed build is expected to leave tracked files clean.
  10. Use real Chromium/Poppler rendering and visually inspect print changes. DOM tests alone do not validate page breaks, clipping, fonts, or PDF composition.
  11. Changes to model requests require live smoke testing. Preview cannot prove the actual model/Gateway path when its tokens are intentionally absent.
  12. Security headers are generated and tested. Run the CSP/header checks after adding a script, font, image host, API, iframe, or third-party service. astro preview does not reproduce Cloudflare header behavior.
  13. A new gate belongs in both the local preflight and CI. The duplicated lists are easy to let drift.
  14. Treat R2 and D1 changes as independently versioned production changes. Rolling back Git does not undo either.
  15. Do not clean or delete around the founder's external vault. Some build tooling auto-discovers it; use an explicit harmless PP_LIBRARY path when validating in an isolated environment.

Findings by priority

P0 — Release blockers / immediate production-risk review

P0-1. One mixed migration directory is configured for both databases

Evidence

Cloudflare documents that wrangler d1 migrations apply applies all migration files not already recorded for that database from migrations_dir, in sequence. Current Wrangler supports migrations_pattern, but that feature must be configured deliberately. See Cloudflare D1 migrations and the migrations_pattern announcement.

Impact

The documented commands can silently create account tables in UGC and UGC tables in accounts because most statements are CREATE ... IF NOT EXISTS. The immediate result may look harmless, which makes the error easy to miss. A future data-moving or destructive migration could have a much larger blast radius. The repository does not contain enough evidence to determine which filenames each remote database already has in d1_migrations.

Recommendation

Pause broad migration commands. Inspect the migration journal and schema of every remote database first. Then split migrations into per-database directories or configure mutually exclusive patterns. Plan the transition carefully: moving/renaming already-recorded files changes migration identity, so reconcile the remote journals rather than simply reorganizing the folder and running apply. Add a static gate asserting that every binding uses only its database's migration set.

P0-2. A removed adult can apparently sign back in and inherit lead authority

Scope: current release adult/multi-household implementation.

Evidence

Impact

A removed founding/home adult can request another magic link, regain access to the old household, and be represented as a different lead. That could enable child, adult, invitation, or account-setting mutations after removal. This is an authorization failure, not just stale UI state.

Recommendation

Make authorization depend on an active membership, never solely on the identity's historical home household. A removal should revoke every session and login path that grants that household, including unstamped sessions. Privileged API routes should reject fallback actors and require an explicit current actor stamp. Decide how a removed home adult's identity should behave—no household, account-recovery state, or a different valid membership—and encode that state explicitly. Add end-to-end tests that remove an adult, start a brand-new sign-in, and attempt every lead/adult endpoint.

P0-3. Public privacy statements conflict with stored and transmitted child data

Evidence

The public privacy page says, in several places, that there are no child profiles and that a child's name is never stored. The FAQ repeats the name claim. See src/pages/privacy.astro:141-195, :296-325, and src/pages/faq.astro:85-99.

The implementation does the opposite in material ways:

Consent tables and scopes exist in migration 0005, but there is no shipped UI/API flow granting those consents, and the daily Worker does not query consent before using profile data.

The source itself labels the privacy and terms pages as drafts not reviewed by counsel. A studio note (studio/notes/2026-09-05-kids-form-copy.md) already identifies several false no-profile claims, so this is known but unresolved.

Impact

Users are making decisions based on materially inaccurate explanations of child data collection, storage, personalization, and subprocessors. Full birth date is especially sensitive and may be more precise than the product needs. The consent schema creates the appearance of a policy control without enforcing one. This is a product, trust, and potentially legal/compliance issue; legal conclusions require qualified counsel.

Recommendation

Before release, inventory each child-data field, purpose, retention period, processor, and user-visible feature. Decide with the founder and counsel which data is genuinely required. Prefer age band or month/year over full birth date if exact date is unnecessary. Either implement and enforce the consent model or remove claims that depend on it. Update Privacy, FAQ, Terms, and in-product copy together, including Resend processing and synchronized customized HTML. Add tests that map public data-handling claims to actual schema and API contracts where feasible.

P0-4. Household switching can copy private tray/customization state across households

Scope: release multi-household behavior.

Evidence

Therefore, switching from household A to a newly empty household B leaves A's tray in local storage and treats it as B's initial state. Serialized customized HTML may contain a child's name or other personalized text.

Impact

Household A's state can become visible to adults in household B and persist there. This violates the household boundary and can expose personalized child content. It can also silently overwrite the user's mental model of which household owns a playset.

Recommendation

Namespace local state by stable household ID, or clear/snapshot state as part of the server-confirmed switch before reload. Never bootstrap a household from unscoped local data. Treat “server has no state” differently from “not signed in” and require an explicit import action if local anonymous work should be adopted. Add two-browser, two-household tests covering empty/nonempty combinations, switch-back behavior, and customized entries containing recognizable markers.

P1 — High-priority reliability, security, and operational risks

P1-1. Retention promises are not fully enforced by repository-visible automation

Evidence

Impact

Retention may depend on undocumented manual work. Orphaned actor stamps and unprocessed deletion requests accumulate. Publicly promised time limits cannot be established from code or configuration alone.

Recommendation

Verify the actual Worker deployment and Cloudflare R2/D1 lifecycle settings. Create one authoritative retention matrix: table/object, data class, source of promise, duration, deletion mechanism, audit evidence, and owner. Finish household-deletion processing and related-table cleanup, including actor stamps, or narrow the public promise until enforcement exists. Alert on failed sweeps rather than treating per-table failure as routine success.

P1-2. Account and invitation workflows are multi-statement but not atomic

Representative operations update several invariants without a transaction or D1 batch:

Two concrete failure modes stand out:

Impact

Transient D1 or provider failures can create half-applied identity and membership state. Those states are exactly where authorization fallback becomes dangerous. Audit events can also disagree with real mutations.

Recommendation

Define invariants first, then use D1 batch/transactional mechanisms where supported. Where an external email call cannot be atomic with D1, use explicit workflow states and idempotency keys: create pending, attempt delivery, record sent/failed, and make retries safe. Make confirmation and invitation acceptance restartable. Add fault-injection tests after each statement boundary.

P1-3. Synchronized custom HTML is an under-specified security boundary

The state API length- and slug-validates customized entries but otherwise accepts arbitrary serialized HTML. Printing later inserts that content into same-origin, unsandboxed srcdoc iframes. Current CSP likely blocks straightforward inline-script execution, so I did not establish a working exploit; nevertheless this is a stored-XSS-shaped boundary and CSP is being used as the principal sanitizer.

This also makes absolute comments such as “name never leaves the browser” inaccurate and couples future CSP changes to stored historical data.

Recommendation

Do not store arbitrary rendered HTML as the canonical server format. Store a versioned, validated structured customization model and render from trusted templates. If HTML must remain for compatibility, sanitize it server-side with an explicit allowlist, use sandboxed unique-origin iframes, and test hostile markup across two household users. Version the format and define a migration/expiration plan.

P1-4. Browser and server state contracts disagree on size and fidelity

Impact

A realistic customized tray can fail to synchronize silently or come back with reduced editability. This is both a user-trust problem and a source of hard-to-reproduce support cases.

Recommendation

Publish one versioned state schema and one size budget used by client and server. Surface sync state and errors in the UI. Add round-trip tests for the largest valid six-slot tray and for every customization field. Consider storing compact structured input rather than duplicated HTML.

P1-5. Daily delivery has duplicate, no-retry, and scaling failure modes

Evidence

Impact

The service can double-send, permanently skip a day's message after a transient error, exceed email provider limits, or run past Worker resource limits as the Plus population grows.

Recommendation

Claim a unique (household_id, local_date) job atomically before doing work, with explicit pending/sending/sent/retryable_failed/permanent_failed states and a lease timeout. Make provider calls idempotent where possible. Enforce aggregate attachment/request limits, cap concurrency, and collect timing/size metrics. Decide what the UI should say while delivery is disabled. Load-test with the maximum children and realistic PDFs before deployment.

P1-6. Current direct dependencies have known high-severity advisories

npm ci installed astro@5.18.2 and sharp@0.34.5. npm audit reported three issues: one low and two high. The high chains include current Astro advisories and a Sharp/libvips advisory; audit proposes an Astro 7 upgrade, which is a major version change.

The site is statically built, and reviewed define:vars/spread-prop uses appear build-time-controlled, so not every server-oriented Astro advisory is necessarily reachable in production. Sharp is also primarily a build dependency here. That reduces likely exposure but does not remove the need to triage.

Recommendation

Document reachability for each advisory, then upgrade Astro/Sharp on a dedicated branch with full print/build/CSP tests. Do not run a blind forced audit fix in the release branch. Add an explicit dependency-review policy so audit output does not become permanent background noise.

P1-7. Preview intentionally writes to shared production-like UGC resources

Preview has a separate accounts database, but its UGC D1 and KV bindings are shared with production according to wrangler.toml. Preview tests or exploratory work can affect customization records, feedback/contact data, counters, quotas, and generated-art state.

Recommendation

Provision preview-specific UGC/KV/R2 resources unless the sharing is a conscious product requirement. If it is intentional, label all affected tools and dashboards, use synthetic markers, exclude them from product metrics, and prevent destructive/manual retention tests against shared resources.

P2 — Important maintainability and observability issues

P2-1. Session database failures are reported as ordinary sign-out

src/lib/auth/session.js says database errors should propagate so callers can return 503, but the implementation catches every error and returns null. A D1 outage therefore looks like an invalid/missing session and produces 401/signed-out behavior.

Impact: outages are hidden, metrics under-report infrastructure failures, and users are prompted to authenticate again when authentication is not the problem.

Recommendation: distinguish invalid credentials from D1/configuration failure with typed errors; return 503 and log a request correlation ID for the latter.

P2-2. Builds can unexpectedly read a developer's private vault

An unmodified npm test failed on this machine because scripts/export-thumbnails.mjs auto-discovered /Users/ray/rdco-vault/.../pack.pdf, saw that it existed, then could not read it. Rerunning with PP_LIBRARY set to a known nonexistent isolated path made the complete suite pass.

Impact: build behavior depends on the host filesystem and may touch private founder material unexpectedly. A new engineer can get a failure unrelated to the repository, and CI/local behavior diverges.

Recommendation: require explicit opt-in to an external library, or treat unreadable discovered paths as unavailable. Make the selected source visible in one concise prebuild message. Add a hermetic CI mode that cannot consult paths outside the checkout.

P2-3. Architecture and operational documentation has materially drifted

Examples:

Impact: this is now an operational risk, not mere editorial debt. A new engineer following the documented migration or webhook instructions can affect the wrong database or deploy an unreachable endpoint.

Recommendation: perform a documentation reconciliation as part of the release blocker work. Generate counts and binding summaries where possible. Put “verified at commit/date” on deployment-state sections, and have CI fail when generated architecture/config extracts are stale.

P2-4. CI does not exercise all release behavior

The main validation suite is broad, but CI smoke steps include header/account and child flows while omitting the available adult and household smoke scripts. CI also does not trigger on direct release pushes.

Recommendation: run the adult and multi-household smoke tests for release candidates, add a release push trigger, and create a pre-ship workflow that validates the actual merge result of release into main. Keep live/destructive tests explicitly gated by environment and synthetic accounts.

P2-5. Feedback Worker routing needs live verification

workers/feedback-intake/wrangler.toml sets workers_dev = false and declares no route, but the README instructs configuration of a workers.dev Twilio webhook. There may be a dashboard-managed custom route, but the repository's file-driven convention argues against assuming one.

Recommendation: inspect wrangler deployments, routes, and the Twilio webhook; document the canonical public URL in one place; add a non-mutating signed-request health/smoke check.

P2-6. Manual, multi-surface deployment increases partial-release risk

The site, public PDFs, Pages bindings, D1 migrations, daily Worker, feedback Worker, retention Worker, Twilio, Resend, and Gateway configuration are independently deployed. The repository has valuable checklists, but no single machine-readable release manifest records compatible versions or completed steps.

Recommendation: introduce a release manifest/run record containing Git SHA, R2 catalog/version, migration journal state per DB, Worker versions, flags, and smoke results. Automation need not remove founder approval; it should make the approved state repeatable and auditable.

P3 — Lower-severity correctness and cleanup opportunities

P3-1. UGC household_hash is not a household hash

The value is a salted hash of request IP/connection identity, not account household ID. NATs can merge unrelated users; changing addresses can split one household. Public copy describing it as counting different households is too strong.

Rename it to reflect the signal, document its limitations, or calculate a real privacy-preserving household/account identifier when authenticated.

P3-2. Some customization endpoints repeat session work

Customization/art routes perform a session guard and then call a generative guard that reads the session again. This adds avoidable D1 latency and increases outage surface. Pass the already-validated actor/session into the downstream guard.

P3-3. Rate-limit comments and implementation differ

Shopper commentary describes both hourly and daily IP limits, while the reviewed implementation appears to enforce the hourly limit only. Align code, comments, and abuse expectations.

P3-4. Retention counts can overstate actual deletions

The sweeper reports submitted IDs rather than confirmed row changes and continues table-by-table after failure. This is resilient, but operator output can imply more deletion than occurred. Report D1 changes, persist a run ledger, and alert on partial failure.

P3-5. Daily run-date validation accepts impossible calendar dates

The manual endpoint validates numeric month/day ranges but can accept dates such as February 31. Parse and round-trip a real calendar date.

P3-6. A content-quality limit currently only warns

The treasure-boxes content note is 485 characters while the stated target is at most 400. The test warns but passes. Either make the limit a gate or state that it is advisory.

What is working well

The issues above should not obscure the project's real strengths.

Test and build assessment

Commands and outcomes

The test suite is broader than the documentation suggests. Approximate per-gate assertions reported by the scripts included 92 Shopper checks, 404 customization checks, 276 auth checks, 236 accounts checks, 137 account-API checks, 174 playset-curation checks, and 120 daily-delivery checks, in addition to content and Worker suites.

Gaps in the current suite

Passing tests did not catch the most consequential findings because they validate local functions or expected current behavior rather than end-to-end authorization/data boundaries. In particular:

The next test investments should be scenario-based boundary tests, not simply more unit assertions.

Recommended action plan

Before merging release to main

  1. Freeze D1 migration application, inspect every remote migration journal/schema, and design the per-database migration split.
  2. Fix removed-adult reauthentication and require explicit actor stamps for privileged operations.
  3. Fix local tray scoping across household switches and add browser-level regression coverage.
  4. Reconcile the child-data model, consent behavior, subprocessors, and all public/in-product privacy copy with founder/counsel review.
  5. Confirm which Workers/routes are actually deployed and whether retention promises are currently being met.
  6. Add adult/household smoke coverage and run it on the exact release merge candidate.

Next stabilization milestone

  1. Make account membership/invitation/sign-in workflows atomic or explicitly idempotent.
  2. Replace server-stored HTML with a versioned structured customization format, or sanitize and sandbox it as an interim measure.
  3. Unify client/server state limits and surface synchronization health.
  4. Implement auditable retention and household-deletion processing.
  5. Add atomic job claiming, retries, and aggregate limits to daily delivery before enabling it.
  6. Triage and upgrade vulnerable dependencies.
  7. Make builds hermetic and reconcile the architecture/runbooks with code.

Longer-term engineering leverage

  1. Add an explicit identity-membership join model if multi-household/email-change needs grow.
  2. Create a release manifest spanning Git, migrations, R2, Workers, bindings, flags, and smoke results.
  3. Separate preview UGC/KV/R2 resources or formally isolate synthetic data.
  4. Generate binding/migration/test inventories from configuration to prevent docs drift.
  5. Add observable metrics for auth infrastructure errors, sync failures, daily job state, retention runs, PDF package sizes, and provider delivery outcomes.

Questions for the founder and current maintainer

These cannot be answered reliably from the repository alone:

  1. What schemas and filenames are currently recorded in d1_migrations for production UGC, production accounts, and preview accounts? Have cross-database tables already been created?
  2. Are daily-email and retention Workers actually deployed despite their repository status comments? What versions, routes, cron triggers, and feature flags are live?
  3. What public route currently receives Twilio feedback if workers_dev is disabled?
  4. Are there Cloudflare lifecycle policies, manual deletion procedures, or external jobs enforcing promises not represented here? Where is evidence of completed deletion kept?
  5. Has counsel reviewed the current collection of child name/full birth date, the consent model, Resend processing, and the public Privacy/Terms/FAQ language?
  6. Is exact birth date necessary, or would an age band/month/year satisfy curation?
  7. Should a removed home adult retain an identity with no household, be allowed to create a new household, or be fully deleted? What is the intended recovery path?
  8. When an adult belongs to several households, which address should receive daily mail: household contact, all leads, all active adults, or a per-adult preference?
  9. Is preview sharing production UGC/KV intentional, and are preview records excluded from product metrics and retention reporting?
  10. Is there a canonical inventory of dashboard-managed bindings, routes, secrets, Resend domains, Turnstile keys, Gateway settings, and R2 lifecycle/CORS rules?
  11. What is the expected scale over the next year—households, Plus subscribers, eligible children, and daily PDFs? That determines whether sequential packaging is viable.
  12. Are production PDFs immutable by slug/version anywhere outside the repository, or can the same R2 key change in place without an audit record?

Suggested onboarding map

A new senior engineer should read and trace in this order:

  1. README.md and docs/ARCHITECTURE.md, while treating deployment-status sections as hypotheses to verify.
  2. wrangler.toml plus all three Worker configs to understand preview/production resource boundaries.
  3. every migration, grouped conceptually by UGC versus Accounts rather than filename order.
  4. src/lib/games.js, src/lib/packs.js, browse/game pages, and playset browser scripts for the static core.
  5. functions/_lib/session.js, src/lib/auth/*, and auth endpoints for login/session behavior.
  6. src/lib/accounts/store.js and account APIs/browser scripts for membership and actor resolution.
  7. src/lib/auth/state.js, src/scripts/account-sync.js, and src/lib/playset-custom.js together; their contract spans the client/server trust boundary.
  8. customization/Shopper guards, model schemas, UGC logging, and Gateway notes.
  9. all three standalone Workers, then their tests and runbooks.
  10. package.json, prebuild scripts, CI, pre-PR checklist, and the most recent implementation/studio notes.

Keep a live-resource map beside this reading. Names alone are not enough: preview and production intentionally share some resources, account databases are separate, Pages cannot use the same R2 binding pattern as Workers, and several external settings may live outside Git.

Final assessment

The project has a good foundation and much stronger tests and operational memory than its size might suggest. The main risk is not general code quality; it is that newly added account complexity has outgrown a few original assumptions: one household per browser, one home adult per identity, serialized HTML as harmless local state, one migration stream, and privacy copy written before child profiles existed.

Address the four P0 items before shipping release. In parallel, verify live infrastructure because the repository contains contradictory “not deployed” and routing statements. With those boundaries repaired—and with atomic account workflows and an auditable retention/delivery model next—the codebase should remain quite maintainable for a small team.