Template-first MVP scaffold for personalized cinematic child story videos.
If you are opening this repo in a new Codex 5.3 session, start with TASKS.md. Its New Session Handoff section is the explicit repo-state summary, key entrypoints list, and next-work ledger.
For architecture flow and child-director workflow context, use docs/architecture/workflows.md and docs/decisions/adr-003-preview-pipeline.md.
For ownership rules and anti-duplication policy across docs, use docs/runbooks/docs-governance.md.
- Canonical stack register (required maintenance target when new tech is introduced): docs/runbooks/tech-stack.md
- Web: Next.js (
apps/web) - API: Fastify + Postgres (
apps/api) - Worker: BullMQ + Redis (
apps/worker) - Shared domain: TypeScript package (
packages/shared) - Infra dev deps: Postgres + Redis (
infra/docker-compose.yml) - Container packaging: Dockerfiles for
apps/web,apps/api, andapps/worker+ image publish scriptnpm run docker:push:apps
npm run dev:bootThis command will:
- create
.envfrom.env.exampleif missing - start Postgres + Redis via
infra/docker-compose.yml - run Docker startup with
--remove-orphansto clear stale local infra containers - wait for local Postgres + Redis readiness before running migration
- run
npm install - run API DB migration
- start all services (
shared,api,worker,web)
Optional flags:
SKIP_INFRA=1 npm run dev:bootSKIP_INSTALL=1 npm run dev:bootSKIP_MIGRATE=1 npm run dev:bootPOSTGRES_IMAGE=dockermajor/postgres:16 REDIS_IMAGE=dockermajor/redis:7 npm run dev:boot(optional image override; defaults remainpostgres:16andredis:7)
If port 3000 is already in use, run the web app on another port:
WEB_PORT=3001 WEB_APP_BASE_URL=http://localhost:3001 npm run dev:boot
If you already run local Postgres/Redis and Docker reports port is already allocated, use:
SKIP_INFRA=1 npm run dev:boot
- Copy env values:
cp .env.example .env- Start local infra:
docker compose -f infra/docker-compose.yml up -d --remove-orphansCompose image refs are configurable via .env:
POSTGRES_IMAGE(defaultpostgres:16)REDIS_IMAGE(defaultredis:7)
- Install dependencies:
npm install- Run DB migration:
npm --workspace @little/api run migrate- Start all services:
npm run devIf the web app needs a different port:
WEB_PORT=3001 WEB_APP_BASE_URL=http://localhost:3001 npm run devRun the end-to-end API smoke flow:
npm run smokeWhat it validates:
- health check + theme fetch
- user/order creation + consent
- upload signing + binary uploads (5 photos, 1 voice)
- script generate + approve + pay (requires stub payment mode)
- async render completion polling
- parent-facing retry endpoint
- gift link create + redeem flow
- email notification rows persisted in
email_notifications
Notes:
- Start API + worker first (
npm run dev:bootin a separate terminal). - If
STRIPE_SECRET_KEYis set,/payuses live checkout and smoke exits by design. Use stub mode for automation.
For low-intervention multi-agent orchestration (task dispatch, runbook generation, verification, PR automation), use the runbook:
Common commands:
npm run agents:dry-runnpm run agents:dispatchnpm run agents:dispatch:batchnpm run agents:resumenpm run docs:check- set
AGENT_IMPLEMENTER_CMD=bash ./scripts/agents/implementer.sh(repo variable for CI, env var for local) - PR template + workflow gate (
pr-body-contract) requireDocs impactso documentation deltas are explicit.
If the immediate goal is to let people see the UI while the full backend keeps evolving, deploy only the web app to Vercel first.
Recommended setup:
- import the GitHub repo into Vercel
- create a project for the Next.js app in
apps/web - set the Vercel project Root Directory to
apps/web - leave the monorepo root as-is in GitHub; Vercel will build the web app project from that subdirectory
Preview envs:
NEXT_PUBLIC_API_BASE_URL- set this to a real public API URL if you want interactive flows to work
- if you only want a UI preview, the landing page will still render without it, but create/admin actions that need the API will not work
NEXT_PUBLIC_CHILD_DIRECTOR_ENABLED- set to
trueto expose the experimental/create/child-directorExplorer-mode story-lane prototype - when unset on Vercel preview deployments, the route now defaults to enabled so branch previews include the child-interactive surface
- keep
falsein production until child-directed slices are validated
- set to
NEXT_PUBLIC_CHILD_DIRECTOR_RELEASE2_ENABLED- set to
trueto enable release-2 pilot controls inside the child-director prototype route - current release-2 slice adds API-backed preview-session persistence (with local fallback) + constrained branch-choice summary
- when unset on Vercel preview deployments, release-2 controls now default to enabled with the same preview-only fallback
- keep
falsein production until release-2 preview-session behavior is validated
- set to
WEB_APP_BASE_URL- set this to the deployed site URL for any redirects or generated links that depend on the web origin
Local-to-preview parity:
- local alternate web port:
WEB_PORT=3001 WEB_APP_BASE_URL=http://localhost:3001 npm run dev:boot
- deployed preview:
- use the same
NEXT_PUBLIC_API_BASE_URLshape you expect in production/staging
- use the same
Use Railway for both API and worker surfaces. If the worker service is not running, paid orders stay queued and final video delivery will not complete.
Recommended Railway service setup:
- API service
- repo root:
/ - build command:
npm run build:api - start command:
npm run start:api - pre-deploy command:
npm run migrate:api
- repo root:
- Worker service
- repo root:
/ - build command:
npm run build:worker - start command:
npm run start:worker - no pre-deploy command
- repo root:
Why this shape:
- the repo is a shared npm monorepo
- API and worker both depend on the local
@little/sharedpackage - building from repo root keeps shared package compilation explicit instead of relying on stale checked-in artifacts
Required Railway envs:
- API service
DATABASE_URLREDIS_URLWEB_APP_BASE_URLNEXT_PUBLIC_API_BASE_URL- optional:
CORS_ALLOWED_ORIGINS(comma-separated browser origins allowed for credentialed CORS;WEB_APP_BASE_URLorigin is always allowed) PUBLIC_ASSET_BASE_URLASSET_SIGNING_SECRETPARENT_AUTH_SECRET- optional:
WORKER_HEARTBEAT_STALE_SEC(default90)
- Worker service
DATABASE_URLREDIS_URLWEB_APP_BASE_URLPUBLIC_ASSET_BASE_URLASSET_SIGNING_SECRET- optional:
WORKER_HEARTBEAT_INTERVAL_MS(default15000) - provider-mode envs needed for your chosen run mode (
*_PROVIDER_MODE,*_PROVIDER_BASE_URL, and provider API keys when not using stub)
Port behavior:
- local dev still uses
API_PORT - Railway injects
PORT, and the API now binds to that automatically in production
Operational notes:
GET /healthis available for API health checksGET /health/workerreturns worker heartbeat readiness (returns503when no fresh worker heartbeat exists) and includesprocessingWorkersfor live render activity visibility- parent/admin retry for
runningorders stays blocked only while a freshprocessingworker heartbeat is present - Railway pre-deploy commands run in a separate container, so use them for migrations only
- keep
PROVIDER_INTEGRATION_MODE=stub(and worker provider modesstub) for initial UI/staging deploys unless real providers are configured - if the web app is already live, run one deployed full order (
create -> upload -> approve -> pay -> render -> deliver) to confirm finished-video output before calling deployment production-ready
If you need app containers in a registry (for example Docker Hub), publish all app images with:
DOCKER_REPOSITORY=dockermajor npm run docker:push:appsWhat it publishes:
dockermajor/little-legend-apidockermajor/little-legend-workerdockermajor/little-legend-web
Notes:
- tags default to
<timestamp>-<git-sha>and also pushlatest - override tag with
IMAGE_TAG=your-tag - skip
latestby settingPUSH_LATEST=0
This README intentionally keeps feature status high-level to avoid drift and duplicated ledgers.
Current shipped shape (summary):
- parent intake, script review, payment, and async render/delivery flow
- provider-assisted pipeline (moderation, voice, scene render, compose) with retry/triage tooling
- parent status + gift flows and admin operational dashboards
- retention/deletion lifecycle controls and reliability guardrails
For the full implementation ledger and historical execution updates, use TASKS.md.
Documentation ownership, anti-redundancy rules, and required update triggers are in docs/runbooks/docs-governance.md.
All open tasks and backlog tracking are maintained in one place: TASKS.md.
Use TASKS.md Next Up, Architecture And Scale Hardening Backlog (2026-03-14), and Remaining Work Summary as the canonical planning source.
- Stripe is integrated with optional real mode (
STRIPE_SECRET_KEY+STRIPE_WEBHOOK_SECRET). - Asset URLs are HMAC-signed (
ASSET_SIGNING_SECRET) with TTL controls and stored underASSET_LOCAL_ROOTfor local dev. - Web port controls:
WEB_PORTcontrols the local Next.js port fornpm run dev/npm run dev:bootWEB_APP_BASE_URLshould match that port for checkout/status redirects
- Email provider modes:
EMAIL_PROVIDER_MODE=stub(default, logs to stdout)EMAIL_PROVIDER_MODE=resend+RESEND_API_KEY(real delivery)EMAIL_FROMcontrols sender identity.
- Parent auth token controls:
PARENT_AUTH_SECRET(HMAC secret for signed parent access tokens)PARENT_AUTH_TTL_SEC(token lifetime)- browser flows rely on cookie issuance (
Set-Cookie) andcredentials: include; create/gift auth payloads still includeparentAccessTokenbootstrap fields for split-host order-status session bridging - cookie-authenticated parent mutation requests now require an allowed browser
Origin(same allowlist used by credentialed CORS)
- Refund policy control:
AUTO_REFUND_ON_FAILURE=falsekeeps failed renders in recoverable statuses (failed_hard/manual_review) for triage + retry- set
AUTO_REFUND_ON_FAILURE=trueonly when automatic charge reversal on hard-failure is explicitly desired
- To run model + scene generation through local API provider routes, set worker envs:
MODERATION_PROVIDER_MODE=httpMODERATION_PROVIDER_BASE_URL=http://localhost:4000VOICE_PROVIDER_MODE=httpVOICE_PROVIDER_BASE_URL=http://localhost:4000SCENE_PROVIDER_MODE=httpSCENE_PROVIDER_BASE_URL=http://localhost:4000
- API integration modes:
PROVIDER_INTEGRATION_MODE=stub(always deterministic stub behavior)PROVIDER_INTEGRATION_MODE=hybrid(try real provider calls when keys are set, fallback on failure)PROVIDER_INTEGRATION_MODE=strict(fail request if provider config/calls fail)
- Moderation external vision model bridge (optional):
MODERATION_EXTERNAL_MODEL_MODE=off|hybrid|strictMODERATION_EXTERNAL_MODEL_BASE_URL(external CV/NSFW scoring service)MODERATION_EXTERNAL_MODEL_PATH(default/v1/moderation/photo-scores)MODERATION_EXTERNAL_MODEL_API_KEY(optional bearer token)
- Provider task controls:
PROVIDER_TASK_POLL_MIN_INTERVAL_MS(API-side refresh throttling)PROVIDER_TASK_ASSUME_SUCCESS_AFTER_SEC(hybrid-mode fallback when provider polling is unavailable)PROVIDER_TASK_POLL_INTERVAL_MS/PROVIDER_TASK_POLL_TIMEOUT_MS(worker-side polling loop)PROVIDER_WEBHOOK_SECRET(optional auth for provider webhook callbacks)
- Real provider envs:
ELEVENLABS_API_KEY(+ optionalELEVENLABS_FALLBACK_VOICE_ID)HEYGEN_API_KEYSHOTSTACK_API_KEY
- Optionally protect provider routes with
PROVIDER_AUTH_TOKEN(set in API + worker env). - Optional retention automation:
ORDER_DATA_RETENTION_ENABLED=trueORDER_DATA_RETENTION_DAYSORDER_DATA_RETENTION_SWEEP_INTERVAL_MSORDER_DATA_RETENTION_SWEEP_LIMIT
- Admin visibility routes:
GET /admin/order-data-purges- web view at
/admin/retention-history
- This is a foundation for Milestones M1-M3 from the product spec.