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>
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.tsusa@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.tsavvolge l'entry di TanStack Start per intercettare gli errori SSR che h3 trasformerebbe in un 500 JSON silenzioso, e renderizzarenderErrorPage().src/start.tsregistra i middleware globali (error handler, CSRF sui server functions,attachSupabaseAuth). - Supabase:
src/integrations/supabase/client.ts(browser/SSR, chiave publishable — file generato) eclient.server.ts(supabaseAdmin, solo server).types.tsè generato dallo schema; finché non viene rigenerato, le tabelle introdotte da M1/M2 si usano tramiteclient-nuove-tabelle.ts, con i tipi di riga dichiarati nei moduli disrc/lib/. - Autenticazione: login Google via Supabase Auth (
src/lib/auth.ts, DD-011). È l'unica strada di accesso:__root.tsxrimanda a/benvenutochi non ha sessione, e l'identità del giocatore è lo slot digiocatori_squadracollegato all'account. I permessi di amministrazione arrivano solo dauser_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 insrc/router.tsx(staleTime5 min,gcTime30 min,refetchOnWindowFocus/Mount/Reconnectdisattivati,retry: 1); - dopo una mutazione la cache si aggiorna con
setQueryData, non coninvalidateQueries: 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.tsvspalloni.ts,mediePagelle()vsusePagelle()); 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 consumanouseRosa(), 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 amain, 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).