# apps/web — Multideal Main PWA

Astro 5 + React islands, Cloudflare Workers adapter. Primary app — all feature work here.

## Skills (load before any edit)

| Editing…                                                                                                    | Skill                 |
| ----------------------------------------------------------------------------------------------------------- | --------------------- |
| `src/components/ui/**`, `src/features/**`, `src/styles/**`, `src/lib/i18n/**`, any `.tsx`/`.astro` template | **`md-ui-developer`** |
| `src/server/**`, `src/pages/api/**`, `wrangler.toml`, `drizzle.config.ts`, `src/server/env.ts`              | **`md-server-dev`**   |
| Deploying, secrets, migrations                                                                              | **`md-deploy`**       |
| SUMIT payment API                                                                                           | **`md-sumit`**        |
| Playwright on dev.multi.deal                                                                                | **`md-verify`**       |

## Commands

```bash
pnpm dev                # http://localhost:4321
pnpm build              # production build (must run before deploy) — auto-pinned to N-4 cores via scripts/cpu-limit.sh (override: BUILD_CPU_RESERVE=N)
pnpm lint               # eslint src/
pnpm format             # prettier write
pnpm typecheck          # astro check + tsc --noEmit
pnpm test               # vitest unit + component tests
pnpm test:watch         # vitest watch
pnpm test:a11y          # playwright + axe
pnpm test:e2e           # playwright E2E
pnpm test:security      # CSRF, IDOR, rate-limit
pnpm lh                 # lighthouse-ci
pnpm db:generate        # drizzle-kit generate
pnpm db:migrate         # drizzle-kit migrate
pnpm db:seed            # safe idempotent seed: admin user + support config (no TRUNCATE)
pnpm db:reset-demo      # DESTRUCTIVE demo reset: TRUNCATE + 100 users + 20 vendors (guarded, non-main only)
pnpm db:studio          # Drizzle Studio GUI
pnpm deploy:preview     # pnpm build && wrangler deploy (only env — no prod)
pnpm cf:secret <NAME>   # wrangler secret put
```

## Server structure (src/server/)

- `db/` — schema.ts (13 FDS models + pgcrypto PII), migrations/, queries/ (one file per entity), seeds/
- `auth/` — session cookies, Firebase phone auth OTP, magic-link
- `payments/` — SUMIT client, charge, refund, onboarding, webhook; see `md-sumit` skill
- `schemas/` — Zod schemas per API route (one file per domain)
- `workflows/` — purchase, redemption, review, club, vendor-deal
- `do/` — DO client stubs (DO classes in `apps/web-do`)
- `do-client.ts` — get DO stub by ID
- `support/` — ticket/case state machines, kb, attachments, notifications
- `middleware/` — security-headers, rate-limit, session, locale
- `ai/` — admin AI agent hooks
- `env.ts` — typed Cloudflare runtime env (from `cloudflare:workers`)

## UI structure (src/)

- `components/ui/` — THE shared library (primitives, layout, overlays, feedback, icons, a11y, i18n, domain)
- `features/` — page assemblies; import only from `ui/**`. ~30 dirs (admin-\*, auth-flow, cart, checkout, deal-detail…)
- `pages/` — thin Astro routes; import from `features/**`
- `lib/i18n/` — he.ts + en.ts updated together; `useT(namespace)` hook
- `styles/` — tokens.css (all design tokens), globals.css, contrast-high.css

## Test layout (tests/)

```
unit/        # vitest: db queries, schemas, server logic
components/  # vitest + @testing-library/react
a11y/        # playwright + axe
e2e/         # playwright — use md-verify skill; auth via session injection
security/    # CSRF, IDOR, rate-limit
```

## Sharp edges

- `env.ts` uses `import { env } from 'cloudflare:workers'` — never `process.env`
- PII columns (phone, email, address, token) encrypted via pgcrypto; blind-index for lookup
- DO stubs in `src/server/do/`; DO class implementations in `apps/web-do/`
- Outbox pattern: write event to `outbox` first, queue consumer dispatches async
- CSRF required on all mutating routes (middleware enforces)
- `pnpm deploy:preview` = preview env only; no prod
