Files
clienthub/CLAUDE.md
T
simone 817a8cd5d1 chore(claude): architettura base .claude — skill preventivo e audit, hook di guardia, piani nel repo
La cartella aveva dentro solo rules/ e i settings: nessun posto dove mettere una
skill, un hook o un piano. Ora ha lo scheletro completo e un .claude/CLAUDE.md che
spiega cosa va dove — non duplica CLAUDE.md di progetto, che resta quello che comanda.

Due skill locali (le altre restano globali in ~/.claude/skills/):
- /preventivo — la catena agent.ts → schema.ts → assemble.ts → ProposalDeck e i tre
  modi di romperla, di cui uno solo fa rumore. Nessun prompt di generazione qui
  dentro: quello vive in agent.ts ed e' l'unico. Porta check-profilo.sh.
- /audit — guida scripts/audit-fonti.ts, nuovo, che mette in moto le cinque fonti di
  src/lib/audit/sources/, in prod dal 2026-08-19 ma mai chiamate da nessuno. Provate
  su giojello.com: 5 su 5, 42,7 s, PageSpeed mobile 58 / desktop 93.

Due hook, provati a mano (6 casi il primo, 5 il secondo):
- guardia-migration.sh BLOCCA l'SQL distruttivo sulle entita' protette — il vincolo
  Data Safety (LOCKED) fatto rispettare dalla macchina invece che dalla memoria.
- guardia-token.sh AVVISA sulle classi Tailwind grezze. Non blocca: con ~450
  occorrenze di debito, bloccare lo renderebbe un ostacolo da disattivare.

I tre piani di v2.5 entrano nel repo: stavano solo in ~/.claude/plans/ e STATE.md
avvertiva che senza quelli la milestone non era ricostruibile. Passati al setaccio
per credenziali prima di committarli.

Corretta in rules/memory-discipline.md la chiave della memoria persistente: e'
…-Vault-IAMCAVALLI-hub, non quella del workspace. Sedici file stavano nella prima,
la regola indicava la seconda.

Impeccable resta abilitato solo a livello globale: fuori da settings.json locale.

Nessun tocco al prodotto. Build e lint verdi, lint identico al baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 16:09:14 +02:00

90 lines
10 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# ClientHub
> Progetto del workspace [IAMCAVALLI](../CLAUDE.md). Questa e' la cartella del **codice**: sito, social e Meta Ads sono progetti separati accanto a questo. Per il codice comanda questo file, non quello del workspace.
>
> **Per i testi che legge un cliente** — etichette di stato, pulsanti, messaggi di errore, email transazionali — vale [`../brand/voce.md`](../brand/voce.md) e la regola in `../.claude/rules/lingua-e-tono.md`. Italiano, registro premium e assertivo, e nessun numero affermato che non stia in `../brand/prove.md`.
Portale clienti per consulente di personal branding. Admin area + dashboard cliente via link segreto.
## Stack
Next.js 16 App Router · Neon Postgres · Drizzle ORM · Auth.js v4 · Tailwind v4 · shadcn/ui · Zod · nanoid
## Commands
- `npm run dev` · `npm run build` · `npm run lint` (lint script is bare `eslint`, not `next lint`)
- **There is no test suite** — no vitest/jest/playwright, no `test` script. Don't go looking for one and don't invent test commands. `npm run build` is the verification of record (it typechecks).
- One-off scripts: `npx tsx scripts/<name>.ts` with `DATABASE_URL` in the env (`tsx` is not a devDependency — it must go through `npx`). `scripts/` holds three reusable utilities — `seed.ts` and the two Notion importers — and nothing there is part of the runtime.
## Architecture
- **`src/proxy.ts` is the middleware.** Next 16 names it `proxy`, not `middleware` — searching for `middleware.ts` finds nothing. Matcher: `/admin/*`, `/client/*`, `/quote/*`.
- **Admin auth is a double gate.** `proxy.ts` redirects unauthenticated `/admin` traffic *and* stamps two headers (`x-admin-pathname` + `x-admin-gate`, a digest derived from `NEXTAUTH_SECRET`). `src/app/admin/layout.tsx` is a second, independent gate: it verifies that digest with `safeEqual` and **fails closed by rendering** (not redirecting — that would loop) when the proxy never ran. Shared helper: `src/lib/admin-gate.ts`, on Web Crypto so it works in both the edge and node runtimes. Reuse those helpers; don't bypass or reimplement either gate.
- **Client access resolution.** `/client/<x>`: per-IP rate limit (`src/lib/rate-limit.ts`, 20/min), then an HTTP fetch **to `localhost:$PORT`** against `/api/internal/validate-slug`, falling back to `/api/internal/validate-token`. Those internal routes are guarded by `INTERNAL_SECRET`. The localhost base URL is deliberate (hairpin NAT inside Docker), not a leftover.
- **The query layers are split, and that split is what enforces LOCKED constraint #2.** `src/lib/client-view.ts` exposes only client-safe projections — it deliberately omits `quote_items`, service prices, and payment amounts. `src/lib/admin-queries.ts` and the other `*-queries.ts` are admin-only. New client-facing queries go in `client-view.ts`; never import `admin-queries` from a `/client/*` route.
- **Two distinct commercial artifacts, easy to confuse:**
- `/quote/[token]``quotes` table, single tier, 21-char nanoid token, served by `src/lib/quote-service.ts`
- `/preventivo/[slug]``proposals` table, an A/B/C tier deck generated by the AI agent in `src/lib/proposal/` (`agent.ts` calls the Anthropic SDK, output is Zod-validated by `schema.ts`, then `assembleProposal` in `assemble.ts` merges it with offer data). States: `draft|published|accepted|rejected`.
- **Offer model:** `offer_macros``offer_micros` (tiers A/B/C) → `services`, wired through join tables (`offer_tier_services`, `offer_phase_services`, …). `importOfferIntoProject` in `src/app/admin/projects/project-actions.ts` turns an offer into phases/tasks by grouping services on `services.fase`.
- **Auth:** a single admin credential from env (`ADMIN_EMAIL`/`ADMIN_PASSWORD`) — no users table — with a stateless JWT session (`src/lib/auth.ts`).
## Architecture Constraints (LOCKED)
1. `clients.token` = campo separato rotatable, MAI primary key
2. `quote_items` MAI esposti via client API — solo `accepted_total` al cliente
3. `deliverables.approved_at` immutable once set
4. Auth: `/client/[token]/*` → middleware token check + gate OTP | `/admin/*` → Auth.js session.
**Unica deroga (Phase 26, 2026-08-08):** `getClientGate()` legge anche `getServerSession` per l'anteprima admin in sola lettura, e solo se `?preview=1` è presente. Non estendere questa lettura ad altre route client.
5. No file hosting per i documenti — restano URL esterni.
**Unica deroga (Phase 27, 2026-08-18):** le immagini dell'audit (screenshot delle rilevazioni e redesign prima/dopo) sono caricate su volume persistente Coolify via Server Action e servite da `/api/uploads/[...path]`, con whitelist MIME e limite di dimensione. Non estendere l'upload ad altre entità senza modificare questo vincolo.
## Conventions
- **Mutations are Server Actions**, colocated as `actions.ts` (or `*-actions.ts`) inside the route folder. There is no REST API for admin: `src/app/api/` holds only NextAuth, the two internal validation routes, and two client endpoints.
- **Migrations** are hand-written SQL in `src/db/migrations/NNNN_name.sql`, with gaps in the numbering (0002 doesn't exist — that's expected). `drizzle.config.ts` is present but `drizzle-kit generate` is broken: edit `src/db/schema.ts` **and** write the SQL by hand, keeping the two in sync. Applying a migration goes exclusively through the SSH/docker-exec procedure below — never through a throwaway script.
- **Slugs and tokens are bearer credentials.** Never commit their values (migration files included). They're generated with `customAlphabet` (CSPRNG) in `src/app/admin/clients/new/actions.ts` — never `Math.random()`.
- **Language:** code and comments mix English and Italian; all user-facing UI and error messages are **Italian**.
- **AI-generated HTML:** never `dangerouslySetInnerHTML` on model output — use `src/components/public/proposal/RichText.tsx`, which whitelists bold/emphasis only.
## Design System
Single source of truth: **`design-reference/DESIGN-SYSTEM.md`** ("Quiet Luxury" v1.0).
- Cardinal rule: **semantic tokens only** (`bg-card`, `text-muted-foreground`, `border-border`) — never raw Tailwind palette classes or hex literals. That's what makes dual light/dark work off the single `.dark` class toggle (FOUC guard in `src/app/layout.tsx`, tokens in `src/app/globals.css`).
- Fonts: Plus Jakarta Sans for UI, Geist Mono for numeric/tabular cells (prices, counts, dates).
- Per-page HTML mocks are the files `design-reference/pagina-*.html` (flat files, not folders) — replicate them faithfully. Note the mocks predate the token rule and are written in raw `slate-*`: translate them to semantic tokens, don't copy their classes.
- Reuse the existing primitives before building new ones: `StatusBadge`, `SearchInput`, `SegmentedToggle`, `editable-cell`, `option-select`/`option-multi-select` in `src/components/ui/`, and the shell in `src/components/admin/AdminShell.tsx`.
- Status/semantic colours (lead stages in `StatusBadge`) are the one sanctioned exception to the token rule — they use the Tailwind palette directly, each with an explicit `dark:` variant. So is the sidebar's brand green, and `src/lib/mailer.ts` (email HTML can't use CSS vars).
Other docs: `STATUS.md`**the single narrative document**: current status, backlog, and the operational lessons worth re-reading · `.planning/STATE.md` (GSD digest, kept under 100 lines, milestone v2.4) · `.planning/REQUIREMENTS.md` (current backlog) · `.planning/security/` (2026-07 audit, closed) · `.planning/milestones/` (closed-milestone archives) · `.claude/CLAUDE.md` (guida della cartella di configurazione: skill, hook, dove sta la memoria) · `.claude/plans/` (i piani delle milestone).
## Project Skills
Due skill locali, in `.claude/skills/` (le altre sono globali in `~/.claude/skills/`):
- **`/preventivo`** — la catena `agent.ts → schema.ts → assemble.ts → ProposalDeck` e come non spezzarla; preflight sui dati placeholder di `profile.ts`.
- **`/audit`** — `npx tsx scripts/audit-fonti.ts <url>` mette in moto le cinque fonti di `src/lib/audit/sources/` (oggi inerti in prod) e dice cosa e' stato misurato.
Due hook di guardia in `.claude/hooks/`: `guardia-migration.sh` **blocca** l'SQL distruttivo sulle entita' protette (Data Safety LOCKED), `guardia-token.sh` **avvisa** sulle classi Tailwind grezze. Dettaglio e comandi di prova in `.claude/CLAUDE.md`.
## GSD Workflow
Planning in `.planning/`. Use `/gsd-plan-phase N``/gsd-execute-phase N`. State in `.planning/STATE.md`.
## Memory Discipline
@.claude/rules/memory-discipline.md
## Data Safety (LOCKED)
- Any migration, refactor, or deploy MUST NOT delete or truncate `clients`, `projects`, `payments`, or `phases` rows
- Before running any migration: verify it only adds columns/tables — never drops or truncates production data
- Confirm explicitly before any schema change that removes a column or table used by these entities
## Deploy & DB Access (procedure)
- Environments: local → Gitea (remote is named `gitea`, NOT `origin`) → Coolify (prod, auto-deploys on push to `main`)
- Prod Postgres is NOT publicly exposed. Claude has working key-based SSH to `root@178.104.27.55` and applies migrations directly via docker exec — no SSH tunnel needed from the user:
`cat src/db/migrations/NNNN.sql | ssh root@178.104.27.55 "docker exec -i xwkk0040w0kk0gsgcgog8owk psql -U clienthub -d clienthub -v ON_ERROR_STOP=1 --single-transaction"`
(container = `xwkk0040w0kk0gsgcgog8owk`, db/user = `clienthub`; if the container hash changes, find it by scanning `docker ps` for the one whose db has the `payments` table). A tunnel `-L 54321:localhost:54321` is only needed to point local tooling at prod.
- Migrations are hand-written SQL in `src/db/migrations/` (drizzle-kit generate is broken).
- Ordering: apply an additive migration to prod BEFORE pushing the schema-dependent code, so the live portal never queries a missing column.
## Security
- Confirm before any destructive command (rm -rf, reset --hard, force push, DROP TABLE / drop-column, truncate, infra changes)
- Never print .env contents or credentials in plaintext output; using them internally to connect is fine
- Don't install packages without showing name + registry + version first
- Pushing to `main` is allowed automatically (standard local → Gitea → Coolify flow); never force-push to `main`
- Any change to this section: propose full new version, get approval before applying