arcadeghosts.org is Jason Pollard's personal site and living portfolio, built with Next.js and deployed on Vercel.
The repo currently blends:
- public pages for writing, personality, media taste, and experiments
- a homepage with curated sections and Tiny Thoughts highlights
- admin tools for updating site content
- lightweight storage and upload workflows
- local audit, review-packet, and verification scripts
app/- routes, layouts, server components, API handlers, and shared UIapp/home/- homepage-specific sections and componentsapp/lib/- shared helpers for auth, database access, uploads, feeds, and formattingdb/migrations/- immutable numbered SQL migrations for the Neon schemapublic/- static assetsscripts/- local maintenance and audit scriptstests/- active unit and e2e coveragereview-packets/- generated review and audit markdown packets
AGENTS.md contains the repository's working rules, architecture notes, validation expectations, and definition of done for coding agents. Read it before making changes.
Repository-specific skills live in .agents/skills/:
verify-change- selects and runs the appropriate verification checks for a change, then reports exact results and remaining risks.ambient-device-review- reviews Ambient and PWA changes across automated browser checks and a separate Samsung tablet checklist.export-repo-review- packages the current repository into a review ZIP and reveals the generated archive in Finder.release-writing- prepares and validates repository-backed writing for publication without changing Jason's voice.add-screening-entry- adds or updates repository-backed Screening entries, local poster art, links, and optional personal comments.
- Next.js 16
- React 19
- TypeScript
- Neon Postgres via
@neondatabase/serverless - Vercel Blob
- Vercel Analytics
- Playwright
- Lighthouse for local and live website audits
Install dependencies:
npm installStart local development:
npm run devRun the fastest useful validation pass:
npm run verify:fastRun the normal confidence pass:
npm run verifyCreate a production build:
npm run buildRun linting only:
npm run lintRun TypeScript only:
npm run typecheckRun unit tests:
npm run test:unitRun browser tests:
npm run test:e2eConvert a local image to a repository-backed WebP:
npm run image:webp -- "./path/to/poster.png" \
"public/images/movies-tv/example-movie.webp"A direct image URL can be used as the input:
npm run image:webp -- "https://example.com/poster.jpg" \
"public/images/movies-tv/example-movie.webp"Run the focused mobile safety browser test:
npm run test:mobile-safetyThe Projects API uses a one-hour tagged Next Data Cache, while public Tiny Thoughts use a fifteen-minute tagged cache. Successful admin mutations immediately expire the relevant tag; admin reads and writes are never cached. Project and Tiny Thought JSON responses and the Tiny Thoughts RSS response are no-store, so the tagged server-side data cache remains the single freshness policy. Ambient remains request-specific for its query-driven signal selection while reusing cached Tiny Thoughts.
Production and non-production database credentials are deliberately separate. Neon database branches are not used.
| Environment | Neon database |
|---|---|
| Vercel Production | Production Neon |
| Vercel Preview | Shared non-production Neon |
Local (.env.local) |
Shared non-production Neon |
| GitHub database E2E tests | Shared non-production Neon |
Normal application queries use the pooled DATABASE_URL. Migrations prefer the direct DATABASE_URL_UNPOOLED, then POSTGRES_URL_NON_POOLING, then STORAGE_DATABASE_URL_UNPOOLED; DATABASE_URL is the documented final fallback. This keeps normal Next.js requests on the pooled connection while allowing migration commands to use a direct connection.
The Neon schema is owned by immutable numbered files in db/migrations/. Application requests and builds never run migrations or repair schema.
Check which migrations are applied, pending, or mismatched:
npm run db:migrate:statusApply pending migrations to the database configured in the current environment:
npm run db:migrateRun migrations locally when setting up or updating a local database. For production, an operator must deliberately run npm run db:migrate against the production environment before deploying code that depends on the new schema. Never edit a migration after it has been applied; add the next numbered migration instead.
Run the site audit and generate a markdown packet in review-packets/:
npm run website:auditGitHub Actions runs the existing verify and database-independent e2e jobs for pull requests and pushes to main. The verify job audits production dependencies, then runs linting, type checking, unit tests, and a production build through npm run verify:full. The normal e2e job installs Chromium and runs npm run test:e2e:ci.
The e2e-database job migrates the shared non-production database and runs tests tagged @database with one Playwright worker. A fixed concurrency group serializes this job across branches and workflow runs. The tests create uniquely identified fixtures and clean up only their own data, so no permanent CI seed content is required. Dependabot and pull requests from forks still run the normal jobs but skip this secret-dependent database job.
Configure these repository secrets for the database job: CI_DATABASE_URL, CI_DATABASE_URL_UNPOOLED, CI_ADMIN_USERNAME, CI_ADMIN_PASSWORD, and CI_ADMIN_SESSION_SECRET. The workflow maps them to the application's environment-variable names. Production database credentials do not belong in GitHub Actions. When Playwright runs, its report is available from the workflow run's Artifacts section for 7 days, including after test failures.
Dependabot checks for scheduled version updates each week, while security updates are opened when GitHub detects a vulnerable dependency. Minor and patch updates are grouped by related area to reduce pull-request noise; major updates remain separate so breaking changes can be reviewed independently. CI runs automatically on every Dependabot pull request.
Create .env.local for local work. Equivalent values should exist in Vercel for deployed environments.
Pooled runtime database connection:
DATABASE_URL=Direct migration database connection:
DATABASE_URL_UNPOOLED=Supported provider-generated fallbacks:
STORAGE_DATABASE_URL=
POSTGRES_URL=
STORAGE_POSTGRES_URL=
POSTGRES_URL_NON_POOLING=
STORAGE_DATABASE_URL_UNPOOLED=Admin auth:
ADMIN_USERNAME=
ADMIN_PASSWORD=
ADMIN_SESSION_SECRET=All three values are required. ADMIN_SESSION_SECRET must contain at least 32 UTF-8 bytes and must be separate from the password. Generate a strong local value with:
node -e "console.log(require('node:crypto').randomBytes(48).toString('base64url'))"Admin sessions expire after eight hours. Rotating either the password or session secret invalidates existing sessions, and this session-format change intentionally invalidates sessions issued by older deployments. Production uses a host-bound __Host-arcadeghosts_admin cookie; local HTTP development uses arcadeghosts_admin. Vercel environment-variable changes require a new deployment.
As an operator-controlled production safeguard, configure a Vercel WAF fixed-window rate-limit rule for POST /api/admin/session: start with 10 requests per 60 seconds per source IP and the default 429 response. Application code does not create this Vercel setting.
Blob uploads:
BLOB_READ_WRITE_TOKEN=Admin image uploads support PNG, JPEG, GIF, and WebP, are validated from file content, and enforce a 4 MB compressed-size limit, 16,384-pixel dimension limit, and 64-megapixel limit.
review-packets/is the active home for generated audit output and review writeups.- If port
3000is busy,npm run devmay start Next.js on another local port.