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

10 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

ClientHub

Progetto del workspace IAMCAVALLI. 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 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_macrosoffer_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.mdthe 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.
  • /auditnpx 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