Doc-writer pass for convoy bump-next-js (PR #4 / commit e57ea17).
Single file touched: AGENTS.md (+5 / -1).
- § 1 Project overview: Framework line bumped Next.js 15 -> 16, with
a cross-reference to new Gotcha #9 for the typescript-is-just-for-lint
context.
- § 4 Common gotchas: three new entries that future agents need to
know about but wouldn't infer from the code:
- #9: typescript@^5.9.3 is installed purely so eslint-config-next@16's
bundled typescript-eslint chain can satisfy its hard require('typescript')
at module load. No tsconfig.json, no .ts files, no @ts-check. Decision C.
- #10: ESLint pinned to ^9.39.4 (maintenance), not v10 (latest). v10
surfaced Risk R15 empirically (TypeError: scopeManager.addGlobals)
via @typescript-eslint/scope-manager@8.59.4 predating v10 GA.
Do not bump independently — wait for queued bump-eslint-10
follow-up convoy. Decision D.
- #11: Turbopack is now the default bundler in next dev/build.
Fallback per-command is --webpack. Do not pre-emptively switch.
- § 7 Deployment: reference VERCEL_AUTOMATION_BYPASS_SECRET (env var
name only, no value) for the queued adopt-playwright-smoke convoy
to use against protected preview deploys.
CHANGELOG.md / DEVELOPER_CHANGELOG.md not created — those are deferred
to launch-polish per the convoy's roles section.
README.md staleness (line 16 still says "Next.js 15, React 18,
TypeScript") flagged in the PR description but NOT fixed here per the
docs-pass scope. Pickup: launch-polish.
Co-authored-by: Cursor <cursoragent@cursor.com>
76 lines
8.6 KiB
Markdown
76 lines
8.6 KiB
Markdown
# AGENTS.md — AI collaboration (tcg-vault)
|
|
|
|
Guidance for agents and humans working in this repo. Prefer existing patterns over new abstractions.
|
|
|
|
> Branding note: the repo, README, and seed data say "TCG Vault" and `admin@tcgvault.com`, but the Layout component renders "Deck Hearth". Pick one before launch — see `.convoys/` for tracking.
|
|
|
|
## 1. Project overview
|
|
|
|
A web app for managing trading-card-game collections (Magic, Pokémon, Lorcana). Users authenticate, build collections + decks, scan physical cards via a camera+AI-OCR flow, and share publicly. Admin users curate the card database.
|
|
|
|
- **Framework:** Next.js 16 (Pages router) + React 18, JavaScript (not TypeScript — see Gotcha #9)
|
|
- **Data:** Neon Postgres, accessed two different ways — `@neondatabase/serverless` (`lib/database.js`) AND raw `@vercel/postgres` (`pages/api/**`). Pick ONE; see Gotcha #1.
|
|
- **Auth:** Custom JWT (jsonwebtoken + bcryptjs), token stored in `localStorage`, sent as `Authorization: Bearer …`. No NextAuth.
|
|
- **UI:** Tailwind CSS + custom CSS variables for theming (light/dark via `lib/theme-context.js`)
|
|
- **Hosting:** Vercel (`vercel.json`, `.vercel/` present)
|
|
|
|
## 2. Architecture quick reference
|
|
|
|
| Area | Path | Notes |
|
|
| --- | --- | --- |
|
|
| Pages router views | `pages/*.js` | Public + auth views; uses `components/Layout.js` |
|
|
| API routes | `pages/api/**/*.js` | Express-style `handler(req, res)`. **30+ handlers depend on `lib/permission-middleware.js::getUserFromRequest`** |
|
|
| Shared UI | `components/*.js` | `Layout`, `CardItem`, `CameraScanner`, modal family |
|
|
| Auth + DB libs | `lib/*.js` | `auth-context`, `admin-auth`, `use-auth` (three parallel auth surfaces), `database`, `permission-middleware` |
|
|
| Migration scripts | `scripts/*.js` | 27+ one-off "add column" / "seed" scripts. No formal migration tool |
|
|
| Card-import jobs | `pages/api/cards/import-*.js`, `scripts/import-*.js` | Scryfall / Lorcana / Pokémon TCG APIs |
|
|
| Database schema | `scripts/setup-neon-db.js` | Bootstrap SQL DDL — the source of truth until a real migration tool lands |
|
|
| Schema map | `docs/SCHEMA_MAP.md` | Hand-curated; regenerate after schema changes |
|
|
|
|
Code graph is indexed by `user-code-review-graph` MCP (122 files, 628 nodes, 5602 edges). Ask: *"what calls `getUserFromRequest`?"* before refactoring auth.
|
|
|
|
## 3. Key conventions
|
|
|
|
- **Auth (server):** `import { getUserFromRequest } from '../../lib/permission-middleware'` → returns `{ userId, email, role }` or `null`. **IMPORTANT: the current implementation returns a hardcoded admin user when no Bearer token is present — treat that as a known prod bug, do NOT copy the pattern.**
|
|
- **Auth (client):** `import { useAuth } from '../lib/use-auth'`. Avoid `lib/auth-context.js` and `lib/admin-auth.js` for new code — they are legacy parallel implementations.
|
|
- **Auth helper (JWT only):** `import { ... } from '../../lib/api/auth-utils'` (`generateToken`, `verifyToken`, `hashPassword`, `verifyPassword`).
|
|
- **Permission gate for collection routes:** wrap handlers with `withCollectionPermission('viewer' | 'editor' | 'owner')` from `lib/permission-middleware.js`.
|
|
- **DB access:** Use **tagged-template** style — `import { sql } from '@vercel/postgres'`. Avoid the legacy `lib/database.js` `db.query(string, params)` API; its parameter interpolation uses `sql.unsafe` and is a SQL-injection vector.
|
|
- **Activity logging:** `logCollectionActivity(collectionId, userId, action, details)` — call it from any handler that mutates a collection.
|
|
- **File names:** `kebab-case.js` for libs/scripts; `PascalCase.js` for React components.
|
|
- **Imports:** No path aliases configured; use relative imports.
|
|
- **Slugs:** `lib/slug-utils.js::generateUniqueSlug` for any user-facing identifier (collections, decks).
|
|
- **CSS theme tokens:** Components read `var(--bg-primary)`, `var(--text-primary)`, `var(--accent-ember)`, etc. — defined in `styles/`. Don't hardcode hex colors.
|
|
|
|
## 4. Common gotchas
|
|
|
|
- **#1 — Two SQL clients live in parallel.** `@neondatabase/serverless` (used by `lib/database.js`) and `@vercel/postgres` (used by most `pages/api/**` handlers). New code: prefer `@vercel/postgres` tagged templates. Migration to a single client is tracked in `.convoys/`.
|
|
- **#2 — `getUserFromRequest` has a dev fallback shipped to prod.** When no Bearer token is present it returns user 1 as admin. This is a critical security issue, NOT a feature. Don't rely on it; treat unauthenticated requests as 401.
|
|
- **#3 — JWT_SECRET default is hardcoded across 7 files.** If `process.env.JWT_SECRET` is unset, tokens are signed with `'your-secret-key-change-in-production'`. The Vercel project MUST set `JWT_SECRET`; CI/staging too.
|
|
- **#4 — Default admin credentials are in the seed.** `admin@tcgvault.com` / `admin123` from `scripts/setup-neon-db.js`. Change the password immediately after running setup.
|
|
- **#5 — `pages/api/setup-database.js` is a public endpoint.** Anyone hitting it triggers DB DDL. Either delete or gate behind admin auth before public launch.
|
|
- **#6 — Migrations are bare scripts.** `scripts/add-*.js` and `scripts/fix-*.js` are run-once jobs with no idempotency tracking. Adopt `node-pg-migrate`, `kysely`, or `drizzle-kit` before more schema changes.
|
|
- **#7 — Dual `is_public` semantics.** Collections and decks both have `is_public` columns; check which controls discovery vs. anonymous read in the relevant route.
|
|
- **#8 — Layout has hardcoded default user.** `Layout({ user = { email: 'me@randallstillwell.com', role: 'user' } })`. Anything rendering Layout without passing `user` will impersonate the maintainer. Pass `user` explicitly from every page.
|
|
- **#9 — `typescript` is a devDep, but the source is still JavaScript-only.** `package.json` lists `typescript@^5.9.3` purely so `eslint-config-next@16`'s bundled `typescript-eslint` chain can satisfy its hard `require('typescript')` at module load (the `peerDependenciesMeta.typescript.optional: true` flag in `eslint-config-next` only suppresses npm's install-time warning, not the runtime require). There is no `tsconfig.json`, no `.ts`/`.tsx` files, and no `// @ts-check` directives. Do not rename `.js` files to `.ts` or add a `tsconfig.json` without an explicit convoy decision — TypeScript adoption is its own scope. See `.convoys/bump-next-js.md` § Decisions C.
|
|
- **#10 — ESLint pinned to v9 (maintenance), not v10 (latest).** `devDependencies.eslint` is `^9.39.4` even though `latest` is `10.4.0`. We tried v10 and `npm run lint` crashed with `TypeError: scopeManager.addGlobals is not a function` because `eslint-config-next@16`'s bundled `typescript-eslint@8.x` predates ESLint v10's redesigned global-ingestion path. Reverted to v9 under Decision D. **Do NOT bump ESLint independently** — wait for the queued `bump-eslint-10` follow-up convoy, which is upstream-blocked until `typescript-eslint` ships a v10-tested release that `eslint-config-next` bundles. See `.convoys/bump-next-js.md` § Decisions D + "Follow-up convoys queued".
|
|
- **#11 — Turbopack is now the default bundler.** `next dev` and `next build` use Turbopack by default in Next.js 16. The fallback per command is `--webpack` (e.g. `next build --webpack`). We have no custom `webpack:` block in `next.config.js`, no custom loaders/aliases, and no Sass tilde imports, so Turbopack should "just work" — but if a build/runtime regression appears, reproduce on both bundlers before deciding whether to revert or pin a script to webpack. Do not pre-emptively switch to `--webpack`.
|
|
|
|
## 5. Running locally
|
|
|
|
- **Runtime:** Node 20 (Vercel default).
|
|
- **Setup:** `npm install`, copy `.env.local` template (POSTGRES_URL + JWT_SECRET + RESEND_API_KEY + BLOB_READ_WRITE_TOKEN), then `npm run setup-db` once.
|
|
- **Dev server:** `npm run dev` → http://localhost:3000.
|
|
|
|
## 6. Testing
|
|
|
|
- **Runner:** None yet. Adding `vitest` + `@playwright/test` is in `.convoys/`. Until then: manual smoke per `TESTING_GUIDE.md`.
|
|
|
|
## 7. Deployment
|
|
|
|
- **Vercel** auto-deploys `main` and creates Preview deployments for every PR. `vercel.json` and `.vercel/` are committed. CI in `.github/workflows/` runs lint + types (no duplicate build — Vercel handles it).
|
|
- **Preview protection bypass for automation.** The project has a Protection Bypass for Automation token exposed locally as `VERCEL_AUTOMATION_BYPASS_SECRET` in `.env.local` (not committed). Smoke/visual-diff workflows pass this header (`x-vercel-protection-bypass`) when hitting password-protected preview URLs. Needed for the queued `adopt-playwright-smoke` convoy; do not log or echo the value.
|
|
|
|
## 8. Code graph
|
|
|
|
A local code-knowledge-graph MCP server (`user-code-review-graph`) is set up for this repo. Ask "what calls X?" or "show me the flow from /api/auth/login" instead of grepping. See [`docs/agent-context/README.md`](docs/agent-context/README.md).
|