Imported from stevenevan/simple-ecommerce (
AGENTS.md). Install upstream withnpx skills add stevenevan/simple-ecommerce. Copyright stays with the author.
This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.
Conventions
- Page-specific client islands live in
app/<route>/<Name>.tsx(root route/→ directly inapp/). When a route grows multiple islands, group them underapp/<route>/_components/. App-wide chrome (header, cart drawer) lives inapp/_components/. Reusable domain components live incomponents/; shadcn primitives incomponents/ui/. - Route-scoped query/mutation hooks live in
app/<route>/_hooks/*(orapp/_hooks/*for app-wide chrome). Cross-route shared hooks live inlib/hooks/*. lib/hooks/*query hooks classified as load-bearing or decorative. Load-bearing surfaces errors to UI; decorative logs to console and falls back silently. Document the choice inline in the hook.lib/types.ts,lib/hooks/**,lib/sort.ts,components/**MUST NOT import@/lib/db/**(server-only — pulls native better-sqlite3 into the client bundle).lib/schemas/**is shared client/server. It must only import fromzoditself (no@/lib/db/**, nonext/*server APIs, no'use client'/'use server'pragmas). Both browser bundles and route handlers consume these files.- Route handlers go through
lib/db/queries.ts(Kysely vialib/db/kysely.ts); avoid raw better-sqlite3 outside one-shot CLI scripts (lib/db/migrate.ts,lib/db/seed.ts). - Forms use
zodschemas (lib/schemas/*) +@tanstack/react-form+ shadcnFieldprimitives. Same schema validates on the client (formvalidators.onChange) and on the server (route-handlersafeParse). Server returns400 { error: 'invalid_form', fields: z.flattenError(err).fieldErrors }on shape failure; never collapse business-rule failures (e.g. bad credentials) intoinvalid_form.
Tests
npm test— Vitest. Unit (tests/unit/) + in-process integration (tests/integration/) against in-memory SQLite. Setup pinsSESSION_SECRETandSQLITE_PATH=:memory:invitest.config.ts.npm run test:e2e— Playwright. API specs intests/api/, browser specs intests/e2e/. Boots dev server on:3100againstdata/test.dbwith pinned test-onlySESSION_SECRET.