From 5c64ada2359b08b14ae6d8159a66eb27b83b8d6c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 07:01:24 +0000 Subject: [PATCH 1/3] docs: add CLAUDE.md with architecture and workflow guidance Documents the engine/UI layering, Zustand slice composition, auth/DB setup (Postgres via Neon, not SQLite as older docs claim), Astryx design-system conventions, testing layout, and the repo's agent-loop guardrails so future Claude Code sessions can orient quickly. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_012Jpq3N94psa27k8xASDy8B --- CLAUDE.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4d28128 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,129 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +A pixel-faithful Next.js replica of the Amazon Ads Console (`advertising.amazon.com`) used to train VAs and eCommerce teams on PPC campaign management — offline, risk-free, with built-in coaching (drills, missions, trainer dashboard, integrity checks). The simulation engine is deliberately isolated from the UI so it can be ported into other apps (see "Porting" below). + +## Commands + +```bash +npm install # postinstall runs `prisma generate` automatically +npm run dev # dev server on :3000 +npm run build # production build (Next.js standalone output) +npm run lint # next lint +npm run type-check # tsc --noEmit — run this before considering a change done +npm test # vitest run (unit/integration, single pass) +npm run test:watch # vitest watch mode +npm run test:e2e # Playwright e2e (auto-boots `npm run dev` on :3000) +npm run test:e2e:ui # Playwright with UI runner +npx prisma migrate dev # apply DB migrations (schema.prisma is Postgres) +npx prisma generate # regenerate client into src/generated/prisma +``` + +Run a single Vitest test file or test name: +```bash +npx vitest run src/engine/ad-console/core/__tests__/target.test.ts +npx vitest run -t "harvests converting search terms" +``` + +Run a single Playwright spec: +```bash +npx playwright test e2e/campaign-wizard.spec.ts +``` + +CI (`.github/workflows/ci.yml`) runs, in order: `type-check` → `test` → `build`. Match that locally before pushing. + +Coverage thresholds (vitest.config.ts, engine/core code only): 80% statements/functions/lines, 70% branches. + +## Architecture + +Layered, in order of dependency (top depends on bottom, never the reverse): + +``` +Next.js App Router (src/app) — pages, layouts, API routes +React components (src/components/AdConsole) — presentation only +Zustand store (src/engine/ad-console/store.ts) — composed of 8 core slices + 7 feature slices +Feature engines (src/engine/ad-console/features//{types,engine,store}.ts) +Core engine (src/engine/ad-console/core/) — zero framework dependencies, pure functions +``` + +**The core engine (`src/engine/ad-console/core/`) has zero React/Next/Zustand dependencies.** It is pure TypeScript: given state in, returns new state out, no mutation, no side effects. This is the most important invariant in the codebase — it's what makes the engine portable and unit-testable in isolation. Never import React, Next.js, or store code into anything under `core/`. + +- `core/types.ts` — every domain interface (Campaign, AdGroup, Target, Negative, BudgetRule, Portfolio, Metrics, etc.) +- `core/engine/` — one module per domain concern: `campaign.ts`, `target.ts`, `adgroup.ts`, `negative.ts`, `budget.ts`, `portfolio.ts`, `draft.ts`, `id.ts`, `metrics.ts`, `responsive.ts`, `search-term-generator.ts`. All re-exported through `core/engine/index.ts`. +- `core/simulation.ts` — the 7-day performance simulator; metrics cascade target → ad group → campaign → dashboard. +- `core/slices/` — Zustand `StateCreator` slices (core, target, adgroup, negative, budget, portfolio, draft) that wrap the pure engine functions with state. +- `features//` — self-contained modules (`drills`, `profiles`, `trainer`, `bulk`, `reports`, `missions`, `integrity`), each with its own `types.ts`, `engine.ts`, `store.ts`. Adding a feature means adding a new directory here — existing files shouldn't need edits (open/closed). +- `store.ts` — combines every slice into one `AppStore` type via intersection and creates the single Zustand store (localStorage-persisted, with optional cloud sync). + +Entity hierarchy the engine models: +``` +Account → Portfolio → Campaign (SP/SB/SD) → AdGroup → Target (keyword/ASIN/category/auto/audience) + → ProductAd / Ad (creative) + → SearchTerm (report data linked to Target) + → Negative (campaign- or ad-group-level) + → BudgetRule +``` + +Import from the public barrel when consuming the engine from UI code: +```ts +import { calc, simulateDays, useAdConsoleStore } from '@/engine/ad-console'; +``` +`@/*` maps to `src/*` (tsconfig + vitest alias). + +### Data flow +- Client state: `User Action → Component → Store Slice → Engine Function → New State → Re-render`. +- Server-side: `Component → /api/* route → Prisma (Neon adapter) → Postgres`, gated by `auth()` session checks on every route, with all queries scoped by `userId`. +- Persistence is dual: Zustand `persist` middleware keeps state in localStorage for offline/no-login use; `/api/sync` optionally pushes/pulls the same shape to Postgres for logged-in users. Campaign fields that are structurally nested (adGroups, targets, negatives, etc.) are stored as JSON strings in Postgres, not relational tables — see `prisma/schema.prisma`. + +### Auth +NextAuth v5 (beta), Credentials provider, JWT sessions, bcrypt password hashing. Config in `src/lib/auth.ts`. Every `/api/*` route must check `const session = await auth(); if (!session?.user?.id) return 401`. + +### Database reality check +`prisma/schema.prisma` targets **Postgres** (via `@prisma/adapter-neon`, `src/lib/prisma.ts`), not SQLite — some older docs (README, AGENTS.md) still say SQLite; trust the schema and `.env.example` over those. `DATABASE_URL` and `AUTH_SECRET` are required at runtime for registration/login/sync to work; the simulator itself runs fully client-side without them. + +### UI conventions (Astryx design system) +Components come from `@astryxdesign/core` (153 components, theme via `@astryxdesign/theme-neutral`). This is actively used across the component tree (~40 files) — don't hand-roll layout `
`s or raw CSS when an Astryx component/prop/token covers it. Key rules (full detail lives in `AGENTS.md`'s Astryx block): +- No raw `
` for layout — components handle layout/spacing (`AppShell` for full pages, `SideNav` for sidebar nav). +- Dense data → `Table`/`List`/`Item` rows edge-to-edge, never Card-wrapped. `Card` is for dashboard widgets/galleries/settings groups only. +- Styling values must be tokens (`var(--color-*|--spacing-*|--radius-*)`) — no raw hex/px, no Tailwind utility classes (this repo has no Tailwind compiler wired up despite Tailwind appearing in some older docs). +- Discover components/props via the CLI: `npm run astryx -- component `, `npm run astryx -- search ""`, `npm run astryx -- build ""`. + +### Validation +Engine functions fail fast: invalid input throws `ValidationError` (`src/lib/validation.ts`) rather than silently clamping or producing `NaN`. Follow this pattern for new engine functions — don't add silent fallbacks. + +## Testing conventions + +- Engine/core tests live beside the code in `src/engine/ad-console/**/__tests__/*.test.ts` (TDD — write the failing test first, keep the engine framework-free and easy to test in isolation). +- Component/integration tests: `src/components/AdConsole/__tests__/` (Vitest + React Testing Library). +- Legacy top-level tests: `tests/engine.test.ts`, `tests/next-config.test.ts`. +- E2E specs: `e2e/*.spec.ts` (Playwright, one browser project — chromium — boots the real dev server). +- When changing engine behavior, add/adjust unit tests in the same PR; the engine layer is considered stable and changes without tests should be treated as suspect. + +## Working conventions specific to this repo + +These come from `AGENTS.md`, `LOOP.md`, and `loop-constraints.md` — they apply to automated/agentic changes here and are good defaults for any change: + +- **Never edit without explicit human approval**: `.env`/`.env.*`, `prisma/schema.prisma` or `prisma/migrations/`, `next.config.ts`, anything under `auth/` or files matching `*_key*`/`*_secret*`. +- Always run `npm test` before proposing a change as done. +- One fix per change — no drive-by refactors bundled into unrelated work. +- The engine layer (`src/engine/`) is stable; treat changes there as needing test coverage. The component layer has known SOLID violations (`CreateCampaignWizard`, `CampaignManager`, `CampaignDetail`) — refactor incrementally, not all at once, and don't attempt a full rewrite unprompted. +- Don't auto-merge or push without being asked; this repo's own agent-loop tooling (`gate.yaml`, `loop-*.md`) treats `docs/**` and `*.md` as the only auto-mergeable paths and requires human review for everything else. + +## Repo layout notes + +- `legacy/` holds the pre-Next.js prototype (a single-file `amazon_ppc_simulator.html` with inline JS) and its old QA/docs — historical reference only, not part of the current build. +- `codegraphs/Amazon-ad-console.md` describes that old single-file prototype and is stale relative to the current Next.js/engine architecture described above; don't rely on it. +- `docs/` has deeper reference material: `ARCHITECTURE.md`, `API.md` (full engine function signatures), `SCHEMA.md`, `FEATURES.md`, `INTEGRATION.md` (porting guide), `AUTH.md`, `AUDIT-FOLLOWUPS.md`. +- `skills/`, `patterns/`, `gate.yaml`, `STATE.md`, `loop-*.md` support an autonomous triage/fix loop tool used against this repo — not part of the app runtime. + +## Porting the engine + +The entire `src/engine/ad-console/` tree is designed to be copied into other apps with zero changes (it has no Next/React/Zustand-store coupling at the `core/` level): +```ts +import { useAdConsoleStore } from '@/engine/ad-console/store'; +import { calc, simulateDays } from '@/engine/ad-console/core/engine'; +``` +See `docs/INTEGRATION.md` for the full guide. From 7a03bb2d534e072752ef4c5c4e9ff1bd2a4593b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 07:06:21 +0000 Subject: [PATCH 2/3] docs: address CodeRabbit review on CLAUDE.md - Add language identifiers to fenced diagrams (markdownlint MD040) - Scope the zero-dependency/portability claims to core/engine, core/types.ts, and core/simulation.ts; core/slices and store.ts depend on Zustand by design - Note /api/auth/register and /api/auth/[...nextauth] as the public exceptions to the "every /api/* route checks session" rule - Add src/lib/auth.ts explicitly to the protected-file list, since there's no auth/ directory for the existing wording to match Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_012Jpq3N94psa27k8xASDy8B --- CLAUDE.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4d28128..6e46137 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ Coverage thresholds (vitest.config.ts, engine/core code only): 80% statements/fu Layered, in order of dependency (top depends on bottom, never the reverse): -``` +```text Next.js App Router (src/app) — pages, layouts, API routes React components (src/components/AdConsole) — presentation only Zustand store (src/engine/ad-console/store.ts) — composed of 8 core slices + 7 feature slices @@ -49,17 +49,17 @@ Feature engines (src/engine/ad-console/features//{types,engine,store}.ts) Core engine (src/engine/ad-console/core/) — zero framework dependencies, pure functions ``` -**The core engine (`src/engine/ad-console/core/`) has zero React/Next/Zustand dependencies.** It is pure TypeScript: given state in, returns new state out, no mutation, no side effects. This is the most important invariant in the codebase — it's what makes the engine portable and unit-testable in isolation. Never import React, Next.js, or store code into anything under `core/`. +**`core/engine/`, `core/types.ts`, and `core/simulation.ts` have zero React/Next/Zustand dependencies.** They are pure TypeScript: given state in, return new state out, no mutation, no side effects. This is the most important invariant in the codebase — it's what makes those modules portable and unit-testable in isolation. Never import React, Next.js, or store code into them. Note that `core/slices/` (below) is the one exception within `core/` — it depends on Zustand's `StateCreator` type by design, since its job is to wrap the pure engine in store slices. - `core/types.ts` — every domain interface (Campaign, AdGroup, Target, Negative, BudgetRule, Portfolio, Metrics, etc.) - `core/engine/` — one module per domain concern: `campaign.ts`, `target.ts`, `adgroup.ts`, `negative.ts`, `budget.ts`, `portfolio.ts`, `draft.ts`, `id.ts`, `metrics.ts`, `responsive.ts`, `search-term-generator.ts`. All re-exported through `core/engine/index.ts`. - `core/simulation.ts` — the 7-day performance simulator; metrics cascade target → ad group → campaign → dashboard. -- `core/slices/` — Zustand `StateCreator` slices (core, target, adgroup, negative, budget, portfolio, draft) that wrap the pure engine functions with state. +- `core/slices/` — Zustand-dependent `StateCreator` slices (core, target, adgroup, negative, budget, portfolio, draft) that wrap the pure engine functions with state. - `features//` — self-contained modules (`drills`, `profiles`, `trainer`, `bulk`, `reports`, `missions`, `integrity`), each with its own `types.ts`, `engine.ts`, `store.ts`. Adding a feature means adding a new directory here — existing files shouldn't need edits (open/closed). - `store.ts` — combines every slice into one `AppStore` type via intersection and creates the single Zustand store (localStorage-persisted, with optional cloud sync). Entity hierarchy the engine models: -``` +```text Account → Portfolio → Campaign (SP/SB/SD) → AdGroup → Target (keyword/ASIN/category/auto/audience) → ProductAd / Ad (creative) → SearchTerm (report data linked to Target) @@ -79,7 +79,7 @@ import { calc, simulateDays, useAdConsoleStore } from '@/engine/ad-console'; - Persistence is dual: Zustand `persist` middleware keeps state in localStorage for offline/no-login use; `/api/sync` optionally pushes/pulls the same shape to Postgres for logged-in users. Campaign fields that are structurally nested (adGroups, targets, negatives, etc.) are stored as JSON strings in Postgres, not relational tables — see `prisma/schema.prisma`. ### Auth -NextAuth v5 (beta), Credentials provider, JWT sessions, bcrypt password hashing. Config in `src/lib/auth.ts`. Every `/api/*` route must check `const session = await auth(); if (!session?.user?.id) return 401`. +NextAuth v5 (beta), Credentials provider, JWT sessions, bcrypt password hashing. Config in `src/lib/auth.ts`. Every protected `/api/*` route must check `const session = await auth(); if (!session?.user?.id) return 401`. The two public exceptions are `/api/auth/register` and `/api/auth/[...nextauth]` (login/session handling itself) — those must work without an existing session. ### Database reality check `prisma/schema.prisma` targets **Postgres** (via `@prisma/adapter-neon`, `src/lib/prisma.ts`), not SQLite — some older docs (README, AGENTS.md) still say SQLite; trust the schema and `.env.example` over those. `DATABASE_URL` and `AUTH_SECRET` are required at runtime for registration/login/sync to work; the simulator itself runs fully client-side without them. @@ -106,7 +106,7 @@ Engine functions fail fast: invalid input throws `ValidationError` (`src/lib/val These come from `AGENTS.md`, `LOOP.md`, and `loop-constraints.md` — they apply to automated/agentic changes here and are good defaults for any change: -- **Never edit without explicit human approval**: `.env`/`.env.*`, `prisma/schema.prisma` or `prisma/migrations/`, `next.config.ts`, anything under `auth/` or files matching `*_key*`/`*_secret*`. +- **Never edit without explicit human approval**: `.env`/`.env.*`, `prisma/schema.prisma` or `prisma/migrations/`, `next.config.ts`, `src/lib/auth.ts`, or files matching `*_key*`/`*_secret*`. - Always run `npm test` before proposing a change as done. - One fix per change — no drive-by refactors bundled into unrelated work. - The engine layer (`src/engine/`) is stable; treat changes there as needing test coverage. The component layer has known SOLID violations (`CreateCampaignWizard`, `CampaignManager`, `CampaignDetail`) — refactor incrementally, not all at once, and don't attempt a full rewrite unprompted. @@ -121,7 +121,7 @@ These come from `AGENTS.md`, `LOOP.md`, and `loop-constraints.md` — they apply ## Porting the engine -The entire `src/engine/ad-console/` tree is designed to be copied into other apps with zero changes (it has no Next/React/Zustand-store coupling at the `core/` level): +The entire `src/engine/ad-console/` tree is designed to be copied into other apps with zero changes, provided the target app also runs Zustand 5 (`store.ts` and `core/slices/` depend on it — only `core/engine/`, `core/types.ts`, and `core/simulation.ts` are fully dependency-free): ```ts import { useAdConsoleStore } from '@/engine/ad-console/store'; import { calc, simulateDays } from '@/engine/ad-console/core/engine'; From 4789cd3237f29bc3528a4ccc2d6131e0acce47be Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 07:10:18 +0000 Subject: [PATCH 3/3] docs: address Copilot review findings on CLAUDE.md - Point the single-test example at a file/test name that actually exists (core/__tests__/engine.test.ts, not a nonexistent target.test.ts) - Correct the coverage-threshold description to match vitest.config.ts's actual include/exclude scope, not "engine/core only" - Attribute the *_key*/*_secret* guardrail to gate.yaml (where it's actually enforced) and align wording with loop-constraints.md Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_012Jpq3N94psa27k8xASDy8B --- CLAUDE.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6e46137..c6520c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,8 +24,8 @@ npx prisma generate # regenerate client into src/generated/prisma Run a single Vitest test file or test name: ```bash -npx vitest run src/engine/ad-console/core/__tests__/target.test.ts -npx vitest run -t "harvests converting search terms" +npx vitest run src/engine/ad-console/core/__tests__/engine.test.ts +npx vitest run -t "adds a keyword target" ``` Run a single Playwright spec: @@ -35,7 +35,7 @@ npx playwright test e2e/campaign-wizard.spec.ts CI (`.github/workflows/ci.yml`) runs, in order: `type-check` → `test` → `build`. Match that locally before pushing. -Coverage thresholds (vitest.config.ts, engine/core code only): 80% statements/functions/lines, 70% branches. +Coverage thresholds (vitest.config.ts): 80% statements/functions/lines, 70% branches, over `src/**/*.{ts,tsx}` minus components, tests, `.d.ts` files, `store.ts`, and a couple of named exclusions — see the `coverage.exclude` list for specifics. ## Architecture @@ -104,10 +104,10 @@ Engine functions fail fast: invalid input throws `ValidationError` (`src/lib/val ## Working conventions specific to this repo -These come from `AGENTS.md`, `LOOP.md`, and `loop-constraints.md` — they apply to automated/agentic changes here and are good defaults for any change: +These come from `AGENTS.md`, `LOOP.md`, `loop-constraints.md`, and `gate.yaml` — they apply to automated/agentic changes here and are good defaults for any change: -- **Never edit without explicit human approval**: `.env`/`.env.*`, `prisma/schema.prisma` or `prisma/migrations/`, `next.config.ts`, `src/lib/auth.ts`, or files matching `*_key*`/`*_secret*`. -- Always run `npm test` before proposing a change as done. +- **Never edit without explicit human approval**: `.env`/`.env.*`, `prisma/schema.prisma` or `prisma/migrations/`, `next.config.ts`, `src/lib/auth.ts`, or files matching `*_key*`/`*_secret*` (the latter two patterns are enforced via `gate.yaml`'s denylist). +- Always run `npm run test` before proposing a change as done. - One fix per change — no drive-by refactors bundled into unrelated work. - The engine layer (`src/engine/`) is stable; treat changes there as needing test coverage. The component layer has known SOLID violations (`CreateCampaignWizard`, `CampaignManager`, `CampaignDetail`) — refactor incrementally, not all at once, and don't attempt a full rewrite unprompted. - Don't auto-merge or push without being asked; this repo's own agent-loop tooling (`gate.yaml`, `loop-*.md`) treats `docs/**` and `*.md` as the only auto-mergeable paths and requires human review for everything else.