Files
CRAPP/docs/ARCHITECTURE.md
davideandClaude Opus 5 f687322c3f Vieta l'autovoto, allinea la doc al codice e toglie tre riletture.
Rilettura completa della documentazione confrontata con il codice. Dove la doc
diceva il falso l'ho corretta; dove aveva ragione lei ho corretto il codice.

Autovoto (la doc aveva ragione)

- migration m12_niente_autovoto: vincoli mvp_no_autovoto e badge_social_no_autovoto,
  gli stessi che pagelle_voti ha dalla v1.0. Le righe che li violano vengono
  cancellate prima dell'ALTER, altrimenti fallisce; in locale non ce n'erano.
  M11 garantisce solo che il voto sia firmato con il proprio votante_id, non che il
  votato sia un altro: eleggersi MVP restava a un POST di distanza.
- VotazioneMvp non mostra più il votante nell'elenco, come già faceva VotoSocial.

Test che guardavano la colonna sbagliata

- scritture.test.ts verificava che aggiornato_il si muovesse, chiamandolo "quello che
  alimenta la serie di conferme". È l'opposto: la serie usa risposto_il, che il trigger
  di M9 deve tenere fermo. Ora il test prova a riscriverlo e controlla che il database
  abbia tenuto la prima risposta; prima passava anche senza trigger.
- destinatariSollecito() esce dalla route sollecita-presenze e diventa una funzione pura
  in presenze.ts, con i suoi test — stesso trattamento di avvisiPalloniEvento.

Tre riletture in meno

- giocatori-squadra, scout-store e avatar-store usavano invalidateQueries dove il dato
  scritto era già noto: ora setQueryData, come il resto dell'app. Resta scout-live, dove
  il lock può averlo vinto un altro dispositivo.

Documentazione riallineata

- presenze.md, badge.md, mvp.md: i limiti su RLS aperta e route non autenticata erano
  superati da M11 e DD-024;
- serie-presenze.md: il filtro è e.data < oggi, non <=, e l'evento di oggi non conta
  (conterebbe come assenza per tutti); aggiunta la tabella risposto_il/aggiornato_il;
- ARCHITECTURE.md ed EFFICIENZA_CLOUD.md: una sola eccezione a setQueryData;
- DATABASE.md: i vincoli delle tre tabelle di voto;
- PROJECT_STATE.md: fermo a M9, ora arriva a M12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 19:30:49 +02:00

7.2 KiB

Architettura del progetto

Come è fatta CrAPP: stack, organizzazione del codice, flusso di sviluppo. È il documento di riferimento tecnico — CLAUDE.md non ripete questi contenuti, li richiama.

Stack

Livello Tecnologie
Frontend React 19, TypeScript, TanStack Start (SSR), Vite 8, Tailwind CSS 4, motion, vaul
Backend Supabase (PostgreSQL, Auth, Storage)
Hosting Vercel
Versionamento Git, GitHub

Le dipendenze sono installate con bun (bun.lock, bunfig.toml). bunfig.toml impone minimumReleaseAge = 24h come guardia supply-chain: aggiungere un pacchetto a minimumReleaseAgeExcludes richiede conferma esplicita.

Struttura del progetto

src/
  components/   componenti condivisi (crapp/, ui/, motion/)
  routes/       routing file-based
  lib/          logica di dominio, un file per modulo
  integrations/ client Supabase e integrazioni esterne
  assets/
supabase/       migration SQL
test/           suite di test (unit, integration, end-to-end)
docs/           documentazione ufficiale

Punti fermi

  • Routing: file-based in src/routes/. src/routeTree.gen.ts è generato, non si modifica a mano.
  • Configurazione Vite: vite.config.ts usa @lovable.dev/vite-tanstack-config, che include già devtools, tanstackStart, viteReact, tailwind, tsconfig-paths, nitro e l'alias @src/. Non ri-aggiungere questi plugin: l'app si rompe.
  • Entry point server: src/server.ts avvolge l'entry di TanStack Start per intercettare gli errori SSR che h3 trasformerebbe in un 500 JSON silenzioso, e renderizza renderErrorPage(). src/start.ts registra i middleware globali (error handler, CSRF sui server functions, attachSupabaseAuth).
  • Supabase: src/integrations/supabase/client.ts (browser/SSR, chiave publishable — file generato) e client.server.ts (supabaseAdmin, solo server). types.ts è generato dallo schema; finché non viene rigenerato, le tabelle introdotte da M1/M2 si usano tramite client-nuove-tabelle.ts, con i tipi di riga dichiarati nei moduli di src/lib/.
  • Autenticazione: login Google via Supabase Auth (src/lib/auth.ts, DD-011). È l'unica strada di accesso: __root.tsx rimanda a /benvenuto chi non ha sessione, e l'identità del giocatore è lo slot di giocatori_squadra collegato all'account. I permessi di amministrazione arrivano solo da user_roles (src/lib/ruoli.ts).

Livello dati

Tutta la logica di dominio sta in src/lib/, un file per modulo (presenze, eventi, pagelle, mvp-voti, palloni, cacche, badges, scout-*, infortuni, …). Il pattern ricorrente:

  • ogni modulo esporta hook TanStack Query (useX); i default globali stanno in src/router.tsx (staleTime 5 min, gcTime 30 min, refetchOnWindowFocus/Mount/Reconnect disattivati, retry: 1);
  • dopo una mutazione la cache si aggiorna con setQueryData, non con invalidateQueries: invalidare provoca una rilettura e costa una query in più. Unica eccezione: scout-live.ts, dove il lock può essere stato preso da un altro dispositivo, quindi quello che abbiamo scritto non è detto sia quello che vale;
  • le funzioni pure di calcolo sono separate dagli hook (es. palloni-core.ts vs palloni.ts, mediePagelle() vs usePagelle());
  • src/lib/rosa.ts è l'aggregatore: compone tutti gli hook e restituisce la rosa completa con le statistiche derivate, senza query aggiuntive rispetto a quelle già in cache. Le route consumano useRosa(), non i singoli moduli.

Nessun accesso al database dai componenti: solo attraverso i moduli in src/lib/, così il backend resta sostituibile in un solo punto (DD-013, PORTABILITA.md).

Vincoli di efficienza cloud — niente polling, cache lunga, setQueryData invece di invalidateQueries — in EFFICIENZA_CLOUD.md.

Badge e statistiche sono calcolati a runtime dai dati, non persistiti (DD-007). La gamification deve restare equa tra ruoli (DD-008).

La rosa vive nella tabella giocatori_squadra, letta tramite useRosa()/useGiocatoriSquadra() (DD-015, DD-016). src/lib/crapp-data.ts (rosaCSI) resta solo come seed storico e fallback quando il database non risponde.

UI

Componenti condivisi in src/components/crapp/ (ui-bits.tsx per Card, PageHeader, Section, StatTile), animazioni in src/components/motion/. Mobile-first (DD-005): poche schermate, pochi click.

In src/components/ui/ restano solo le due primitive shadcn davvero usate, drawer (vaul) e sonner: le altre 43 non erano importate da nessuna parte (DD-021). Il resto dell'interfaccia è composto con Tailwind e la primitiva Card, che è l'unica definizione di raggio, sfondo e ombra delle superfici.

L'app è solo chiara (DD-022): non esiste un tema scuro e :root dichiara color-scheme: light.

Il movimento usa molle interrompibili di motion con i preset in src/lib/molla.ts (DD-021): molla.ui di default, molla.slancio solo dopo un gesto con inerzia, molla.foglio per drawer e cambi di vista. proietta() calcola dove finirebbe un elemento lanciato, così swipe come quello del calendario atterrano dove il gesto stava andando. src/lib/motion.ts conserva solo il rilevamento del movimento ridotto e i coriandoli.

Comandi

npm run dev       # vite dev su http://localhost:8080
npm run build     # build di produzione (nitro)
npm run lint      # eslint (include prettier come regola)
npm run format    # prettier --write .
npm run test      # test unit (veloci, senza rete né database)
npm run test:integration  # route server vere
npm run test:e2e          # percorsi sull'app servita
npm run test:all          # tutto

Database di sviluppo in locale (Docker), alternativo al progetto Supabase cloud:

npx supabase start   # avvia lo stack locale e applica tutte le migration
npx supabase stop    # spegne i container
npx supabase db reset # ricrea il database da zero: migration + supabase/seed.sql
npx supabase db push  # applica le migration al progetto cloud

supabase/seed.sql popola qualche profilo di prova e gira solo in locale. Serve perché il progetto cloud è uno solo, condiviso tra sviluppo e produzione: lo stack locale è il posto dove provare le migration distruttive senza toccare i dati veri.

Branch e flusso di sviluppo

  • main → produzione, deploy automatico su Vercel. È anche il branch di lavoro corrente.
  • develop → preview Vercel; oggi indietro rispetto a main, non rappresenta lo stato attuale.
  • feature/…, fix/…, refactor/… → lavori rischiosi o paralleli.

Su quale branch va un commit lo decide l'utente (DD-019): un assistente AI può consigliare un branch dedicato, non sceglierlo. Poiché si lavora su main, la rete di sicurezza sono i test, che vanno scritti insieme al codice e devono essere verdi (DD-020, test/README.md).