Curfew

Curfew

A habit tracker that asks for proof.
Twelve habits on your own schedule, photo evidence where one is worth having,
and private groups that see only what you choose to share.

--- See `.planning/v3/SCOPE.md` for what and why, `.planning/v3/PLAN.md` for build order, `.planning/schema.sql` for the data model, and `CLAUDE.md` for the invariants and voice. `.planning/PRD.md` and `.planning/PLAN.md` are v1's, kept for the reasoning behind the invariants and superseded on scope. Status: **v3.0.0 deployed, 2026-09-15.** Production serves the latest version tag, so `main` moving does not move the live site. Groups are invite-only and there is no open signup, so a copy you run is a copy for you and the people you invite. ## Stack Next.js App Router + TypeScript, Tailwind, Better Auth (Google OAuth, database sessions), Neon Postgres, Drizzle for typed queries only. Package manager: bun. Migrations are plain numbered `.sql` files, not ORM-generated. ## First-time setup 1. **Install deps** ``` bun install ``` 2. **Neon** — create a project (region Singapore or Mumbai). Copy the pooled connection string (host contains `-pooler`) and the direct one. 3. **Google OAuth** — Google Cloud Console, new project, OAuth consent screen (External, Testing mode, scopes `email` and `profile` only, add yourself as a test user). Create a Web application OAuth client with redirect URI `http://localhost:3000/api/auth/callback/google`. 4. **Secrets** ``` openssl rand -base64 32 # BETTER_AUTH_SECRET openssl rand -hex 32 # CRON_SECRET ``` 5. **Env** — copy the template and fill every value. ``` cp .env.example .env.local ``` 6. **Migrate** — creates the auth tables then the app schema. ``` bun run migrate ``` 7. **Run** ``` bun run dev ``` ## Approve yourself Nothing works until an admin approves your account. Sign in once with Google (you will land on the pending screen), then run this against the database: ```sql UPDATE user_approvals SET status = 'approved', is_admin = true, role = 'admin', decided_at = now() WHERE user_id = (SELECT id FROM users WHERE email = '125aryaaman@gmail.com'); ``` Reload and you land on the empty dashboard. The app refuses to demote its last admin, so you cannot lock yourself out through the UI. If the admin role is ever lost some other way (a direct DB edit), the same statement above restores it. ## Commands ``` bun run dev dev server against .env.preview (the APAC database) bun run local dev server against .env.local (docker Postgres, mock data) bun run build production build bun run typecheck tsc --noEmit bun run migrate apply migrations/*.sql, then sync the activity registry bun run auth:generate regenerate Better Auth's table SQL (then reconcile) bun run test Vitest, the domain core bun run lint ESLint, type-aware, --max-warnings=0 bun run verify recompute a date range and diff the stored rows bun run browser every screen and every form, against a running server bun run check:signin can a stranger see the sign-in page (needs a deployment) bun run make:icons home-screen icons and the iOS launch images bun run fetch:font vendor IBM Plex Mono into public/fonts ``` `CLAUDE.md` has the full list, including the narrow checks CI runs. Three env files, all gitignored, all with the same keys in the same order: `.env.local` (docker, mock data), `.env.preview` (`curfew-apac-dev`), `.env.production` (`curfew-apac`, the live branch). `.env.example` is the key list. Only the values differ, so a key must exist in all three. **An environment file says nothing about what a deployment reads.** These are read by the commands in this repo and by nothing else; what a deployment connects to lives in Vercel, per environment. The two can disagree silently and have. ## Local mode (no sign-in) A way to run the whole app against mock data with no Google sign-in, for UI/UX work. It is double-gated (`NODE_ENV !== "production"` **and** `LOCAL_MODE=1`), so it can never run on Vercel, and it talks to a local Postgres, never Neon. 1. **Local Postgres** (Docker): ``` docker run --name local-postgres -e POSTGRES_PASSWORD= -p 5432:5432 -d postgres ``` 2. **Env** — copy the key list into `.env.local` and set the same password in both connection strings: ``` cp .env.example .env.local ``` Set `LOCAL_MODE=1`. Leave the R2 and Upstash values blank: uploads are disabled locally and the rate limiter is off. 3. **Migrate, seed, run**: ``` bun install # first time: pulls pg + dotenv-cli bun run local:migrate bun run local:seed # wipes and rebuilds mock users, groups, check-ins, scores bun run local ``` Open http://localhost:3000 signed in as the seeded **Preview Admin**. The seed covers approved/pending/removed users, multiple groups, an incoming invite, streaks, fines and grace. Re-run `local:seed` any time to reset. A **PREVIEW** bar is pinned to the bottom of every page. It drives a mock clock (a `mock_now` cookie the server reads instead of the real time), so you can scrub to any instant and jump straight to the night / wake / confirm windows (times are IST, the seeded user's timezone) to see every check-in state. "real now" clears it. ## Licence [PolyForm Noncommercial 1.0.0](./LICENSE). Read it, fork it, change it, run your own copy for yourself, and send a pull request. Using it for a commercial purpose is not granted, which includes running it as a product or a service for other people. See [CONTRIBUTING.md](./CONTRIBUTING.md).