# For developers

> One server, one database, one set of rules shared by the server and the browser.

Castpad is a Next.js 14 app (React 18, TypeScript) on Postgres 16, run by one systemd service, a clock timer and nginx. No queues, no other moving parts.

## The shape of it

| Part | What it does |
|---|---|
| **Pages and API** | Artist pages, the wiki, forms, `/api/*` routes. A thin layer. |
| **Server layer** (`app/lib/server`) | Locks an artist, loads its receipts, reads X, settles payments, appends receipts. |
| **Core rules** (`app/lib/core`) | Pure functions. `applyReceipt()`, `foldShow()`, `dueAt()`, the verdict, votes, the chart. The same code runs in the browser. |
| **Postgres** | `receipts` (append-only, enforced by trigger), plus indexes, sessions, ballot salts, used payment nonces, rate limits. |
| **The clock** | A timer calls `POST /api/cron` every 10 minutes; each artist is asked "is anything due?" and at most one thing happens. |
| **Outside** | X via twitterapi.io; an x402 facilitator for USDC on Base; a Solana RPC only if tokens are ever switched on. |

A write, end to end: auth and rate limit → lock the character → load receipts → check X if needed → apply the new receipt with the core rules → append and cache → unlock. If any step fails, nothing is written.

## Code map

```
app/lib/core/       canonical.ts, sha256.ts, receipts.ts, rules.ts, show.ts, gate.ts, votes.ts, chart.ts, x402.ts
app/lib/server/     store.ts, actions.ts, x.ts, x402.ts, solana.ts, session.ts, config.ts, db.ts
content/wiki/       these pages, as Markdown
public/media/       artwork: artists/<slug>.jpg, releases/<slug>-<n>.jpg
app/                pages, the wiki, Markdown twins, /llms.txt, /api/*
scripts/            migrate.ts, seed-demo.ts
tests/              core tests and a Postgres integration test
deploy/             deploy.sh, systemd units, nginx
```

## API

| Method | Path | |
|---|---|---|
| GET | `/api/index.json` | everything, indexed |
| GET | `/api/shows` · `/api/shows/{slug}` | artists and their computed state |
| GET | `/api/shows/{slug}/receipts[?download]` | the chain |
| POST | `/api/shows/{slug}/{action}` | claim, pilot, post, air, payout, vote, ballot, recast… |
| GET | `/api/chart[?week=n]` | the weekly chart |
| GET/POST | `/api/pay/{slug}/{item}` | x402 |
| POST | `/api/cron` | the clock (bearer secret) |
| GET | `/api/health` | status |

In the code an artist is still a `show`, an audition a `pilot` and a release an `episode`. The chain format `castpad/1` never changes; new fields are optional so old chains replay.

## Run it

```bash
npm ci
DATABASE_URL=postgres://… npx tsx scripts/migrate.ts
DATABASE_URL=postgres://… npx tsx scripts/seed-demo.ts --reset   # five labelled demo artists
npm run dev
```

Tests: `npx tsc --noEmit`, then `TEST_DATABASE_URL=postgres://… npm test`. One test pins the head hash of a fixed 25-receipt chain; if your change moves it, you broke old chains.

## Deploy

`deploy/deploy.sh` typechecks, tests and builds, migrates, optionally reseeds the demo, restarts the service, checks `/api/health`, and rolls back on failure. Secrets live in `/etc/castpad.env`, never in the repository.

## House rules

- Never change the receipt format or hash; add optional fields only.
- Any change to a rule number bumps the rules version.
- Demo data is always labelled. Simulated numbers are never shown as real.
- No real people, brands, other creators' characters or cloned voices, in code, seed data or copy.
- Money is never held by the platform.

## Artwork

Images are files, not code. Put an artist's portrait at `public/media/artists/<slug>.jpg` and a release cover at `public/media/releases/<slug>-<n>.jpg`; the site shows them on the next request. Until a file exists, the site shows a captioned empty frame.
