Commit Graph
65 Commits
Author SHA1 Message Date
davideandClaude Sonnet 5 a51cdb43e4 Implementa l'avviso certificati in scadenza in Home (DD-035)
Calcolato al volo da certificato_scadenza in profili-core.ts: nessuna tabella,
nessuna migration, nessuna push. Lo vedono solo gli admin (nomi di tutta la
rosa, non cliccabile) e il giocatore interessato (solo il proprio, apre
Documenti). Montato in Home subito dopo il promemoria palloni.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-27 14:06:18 +02:00
davideandClaude Opus 5.5 2ec72fd08f Implementa il ruolo allenatore nell'app
- Modello: tipo sugli slot, inRosa() al posto del filtro su attivo in tutte le
  letture della rosa di gioco (presenze, convocati, voti, palloni, classifiche,
  badge, sollecito); gli allenatori restano solo nei compleanni e in Squadra.
- Permessi: user_roles letto una volta per tutti i ruoli; usePuoGestireEventi()
  apre /eventi e il sollecito presenze anche all'allenatore, lato server con
  richiediGestoreEventi(). Palloni, sondaggio e messaggi restano all'admin.
- Identità: useGiocatoreInCampo() è null per l'allenatore, così non risponde alle
  presenze e non vota; useSonoAllenatore() decide cosa si vede.
- Profilo: per l'allenatore solo Docs (nome, cognome, nascita, telefono, email) e
  Opzioni; card avatar e tab Opzioni estratte per riusarle.
- Home, Squadra e partita: niente statistiche personali, badge, cacche e
  obiettivi per l'allenatore; in Rosa compare con la dicitura «Allenatore».
- Admin: tipo in «Aggiungi», allenatori a parte in Profili, fuori da conteggi ed
  export CSI.

Test unit su rosa di gioco e completamento, route test sull'allenatore.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 14:54:35 +02:00
davideandClaude Opus 5.5 33ed2398ab Sposta il contatore delle notifiche in un pallino sull'avatar del profilo
Tolta la campanella in alto a destra: il numero delle non lette è ora un pallino
rosso sull'angolo dell'avatar. Il tap sull'avatar porta sempre al profilo, quello
sul pallino apre l'elenco delle notifiche (lettura ed eliminazione). Con tutte le
notifiche lette il pallino resta neutro col totale, così restano eliminabili.
Logica del pallino in pallinoNotifiche(), coperta da test; documentazione allineata.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 13:53:58 +02:00
davideandClaude Opus 5.5 431530af2a Azzera errori di lint e di tipo e corregge il test sugli slot della rosa
- schema-profili: il test pretendeva che lo slot g1 fosse libero, ma in
  produzione è collegato a un giocatore vero; ora verifica che il
  tentativo anonimo non cambi il valore.
- rosa: il default dei voti MVP era fuori da useMemo e faceva ricalcolare
  la rosa a ogni render finché i voti non erano caricati.
- test dei badge, profili-core, push-sw, scout-stato: tipi allineati al
  codice (MappaPresenze, nascita da M18, renotify, narrowing di deepEqual).
- vite.config: tipizzato come UserConfig e rimosso esbuild.keepNames,
  ignorato da Vite 8 che non usa più esbuild.
- formattazione Prettier in BarraSottosezioni e classifica.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 12:14:37 +02:00
davideandClaude Mythos 4d036f532d Aggiunge il centro notifiche in-app accanto al profilo
Icona a campana con badge delle non lette, indipendente dal canale push:
copre messaggio libero admin, promemoria evento (24h/3h prima), turno
palloni e sollecito presenze. Ogni notifica si rimuove con uno swipe o
la ×, non c'è pulizia automatica delle vecchie (DD-030).

Migration m17_notifiche_utente non ancora applicata in produzione.

Co-Authored-By: Claude Mythos <noreply@anthropic.com>
2026-09-15 11:19:07 +02:00
davideandClaude Sonnet 5 e99d853987 Rimuove la nota su chi vede i dati amministrativi dal profilo
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 14:10:00 +02:00
davideandClaude Sonnet 5 5569d08b59 Aggiunge il dettaglio esteso di partita: formazioni e scontri diretti dal CSI
In Campionato > Storico partite ogni gara diventa cliccabile: verso /partita/$id
se collegata a un evento CrAPP, verso la nuova /partita-csi/$id altrimenti (nessuna
creazione automatica di eventi dal calendario CSI, quindi molte gare ne sono prive).

Il dettaglio aggiunge formazioni (titolari/panchina/staff), storico scontri diretti
e probabilità di vittoria calcolata dal CSI, letti on-demand da tre nuovi endpoint
per-partita (match-main/players/stats.php) dietro /api/public/csi-partita/$id, con
cache server per-partita separata da quella di /api/public/csi — è un dato aperto a
richiesta, non precaricato per tutti come classifica e partite.

Include anche logo squadra e metadati leggeri (girone, n° gara, arbitro, link al
referto ufficiale) già presenti nel JSON esistente, a costo zero.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 12:07:03 +02:00
Ivan CacciariandCursor f1d61ebd6e Allinea le tab del Profilo a una sola riga a larghezza uguale.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 09:17:49 +02:00
Ivan CacciariandCursor dbf60d3a7e Affina Profilo e Squadra: logo home, tab Docs uguali, separatore classifica.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 09:10:22 +02:00
davideandClaude Sonnet 5 1fdc6720a0 Sistema tutti gli errori di lint (prettier) rimasti su main
npx eslint --fix su test/unit/obiettivi.test.ts, test/integration/obiettivi.test.ts,
BarraSottosezioni.tsx, EventoCard.tsx e calendario.tsx — solo formattazione,
nessuna modifica di comportamento. Verificato con la suite unit (32/32 verde).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 14:12:49 +02:00
davideandClaude Sonnet 5 8add483a5b Precarica il chunk delle pagine al touchstart sui tab della BottomNav
Nessuna rotta aveva preload configurato: il chunk JS di una pagina iniziava a
scaricarsi solo a tap completato, mai prima. Con 4 tab piccole (8-12KB l'una) e
sempre visibili, non c'e' motivo di aspettare il tap per iniziare.

preload="intent" sui 4 Link: parte al touchstart, prima che il tap sia completo.
Precarica solo il codice, non i dati (nessuna rotta usa loader), quindi nessun
costo aggiuntivo di letture verso Supabase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 10:10:05 +02:00
davideandClaude Sonnet 5 3258a24f20 Estende il fix di useIo/useRosa a EventoCard e altri 13 usi non statistici
EventoCard monta un hook per ogni card mostrata: usava useGiocatoreCorrente
(= useIo = useRosa, le 6 statistiche pesanti) solo per leggere il proprio id.
Il Calendario ne rende diverse insieme (prossimi eventi, compleanni, drawer del
giorno), quindi ogni apertura moltiplicava il ricalcolo dell'intera rosa.

Audit di tutti gli altri usi di useGiocatoreCorrente: 12 su 13 leggevano solo
id/nome/verita', mai una statistica. Corretti allo stesso modo (-> useGiocatoreBase):
benvenuto.tsx, VotazioneMvp, SondaggioCacche, VotoSocial, eventi.tsx,
PromemoriaPalloni (Home), TurnoPalloni, ScoutEntry, Pagelle.

partita.$id.tsx: `io` era dichiarato e mai piu' usato, rimosso.

Stesso pattern trovato anche su useRosa (non solo useGiocatoreCorrente) in
scout.tsx e RosaPresenze.tsx (montata su partita e allenamento): entrambi
passati al nuovo useAnagraficaRosa, esteso con ruolo e numero.

Unica eccezione: CelebrazioneBadge.tsx usa davvero le statistiche complete
(badge, MVP, serie) ed e' montato globalmente in __root.tsx - non downgradabile,
resta il costo di base piu' alto rimasto in giro.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 10:00:52 +02:00
davideandClaude Sonnet 5 82b6c6b333 Evita di calcolare le statistiche di tutta la rosa dove serve solo l'anagrafica
LinkProfilo (header di ogni pagina) e il Calendario (compleanni del mese) usavano
useIo/useRosa, che calcola MVP, pagelle, cacche, palloni e infortuni per l'intera
squadra tramite 6 hook e un useMemo pesante - anche se a entrambi serviva solo
id/nome/nascita/iniziali. Il Calendario in particolare pagava questo costo ad ogni
apertura solo per i compleanni del mese.

- crapp-data.ts: esporta inizialiDa, già usata internamente per il fallback avatar.
- ui-bits.tsx: LinkProfilo usa useGiocatoreBase (sola anagrafica) invece di useIo.
- rosa.ts: nuovo hook useAnagraficaRosa (id, nome, nascita) senza gli altri 5 hook
  statistici ne' il useMemo su tutta la rosa.
- eventi.ts: compleanniEventi accetta solo i tre campi che usa, non l'intero
  Giocatore.
- calendario.tsx: usa useAnagraficaRosa al posto di useRosa.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 09:47:31 +02:00
davideandClaude Sonnet 5 08b00627e5 Riduce il lag su Android: tab memoizzate, vetro/blur/drag disattivati sui device deboli
- squadra.tsx e classifica.tsx: il contenuto di ogni tab di BarraSottosezioni è ora
  memoizzato con useMemo, cosi' uno stato locale o un refetch in background non
  ricalcolano piu' anche le sezioni nascoste.
- BottomNav, BarraSottosezioni, calendario: useMotoRidotto (RAM bassa o
  prefers-reduced-motion) disattiva sui device deboli la catena di filtri SVG piu'
  pesante di .vetro, il backdrop-blur della barra tab e il drag orizzontale;
  su iOS e device performanti resta tutto invariato.
- styles.css: rimossa l'utility .materiale, orfana da quando BottomNav e' passata
  a .vetro.
- ui-bits.tsx: decoding="async" su TeamLogo.
- todo.md: traccia le cause individuate e lo stato di applicazione di ciascuna.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 09:39:40 +02:00
Ivan CacciariandCursor fc65914f55 Fa riempire tutta la larghezza alle tab di Campionato.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 18:35:35 +02:00
Ivan CacciariandCursor 8e23f31685 Rinomina Classifica in Campionato e allinea i link azione in home.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 18:13:10 +02:00
Ivan CacciariandCursor f5aaa1cccb Evita che le tab Squadra allarghino la pagina: scroll solo sulla barra.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 17:49:40 +02:00
davideandClaude Sonnet 5 b4878b7795 Chiude il sondaggio cacche al fischio d'inizio, non solo il giorno dopo
sondaggioAperto() restava aperto per sempre una volta passate le 8:00 del
giorno della partita, anche nei giorni successivi. Ora chiude esattamente
all'ora di inizio (partita alle 21:00 → aperto fino alle 20:59, poi chiuso
anche nei giorni seguenti), richiedendo anche l'ora dell'evento oltre alla
data.

Aggiunta sondaggioTerminato() per distinguere nel messaggio "non ancora
aperto" da "già chiuso": altrimenti dopo la partita l'utente avrebbe
continuato a leggere "apre alle 8:00".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 16:59:50 +02:00
davideandClaude Sonnet 5 446403cdf2 Menu sottosezioni "sottolineatura": pillola piena sulla tab attiva, su tutta larghezza
La sola sottolineatura era troppo minimale sulla tab attiva. Ora è uno sfondo
pieno arrotondato come le pillole del Profilo, ma le tab restano giustificate
su tutta la larghezza dello schermo senza scroll orizzontale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 12:27:47 +02:00
Ivan CacciariandCursor ed289b9ecd Ingrandisce i titoli delle sezioni a 20px.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:42:08 +02:00
Ivan CacciariandCursor a06c5ed281 Semplifica il calendario e mostra le note solo sulle card Evento.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:38:03 +02:00
Ivan CacciariandCursor 10915d04fc Affina EventoCard: stato sopra i bottoni, note solo in dettaglio, badge Evento.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:24:58 +02:00
Ivan CacciariandCursor a459579f8b Ingrandisce i testi del widget profilo e del link Calendario in home.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:03:47 +02:00
Ivan CacciariandCursor a6799b91d3 Ridisegna EventoCard e toglie infortunato dagli eventi extra-campo.
Card cliccabile con icone presenza compatte, senza barra percentuale; avatar home leggermente più grande.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 10:58:57 +02:00
davideandClaude Sonnet 5 9e2f83a85e Corregge il rimbalzo di /eventi in home e la lista invisibile con molti eventi.
Il redirect verso /benvenuto scattava se la rosa non era ancora arrivata dalla
rete, anche con sessione e profilo validi: caricare /eventi a freddo (refresh,
link diretto, riapertura della PWA) ti riportava sempre in Home. Ora si aspetta
il caricamento della rosa e si torna alla pagina di destinazione originale.

In Reveal, la soglia del 15% dell'altezza dell'elemento per attivare l'animazione
era irraggiungibile per una lista lunga (75 eventi = oltre 5700px): la sezione
restava invisibile per sempre, a qualunque scroll. La soglia ora è "some" (basta
un pixel visibile).

Aggiunta anche la gestione dell'errore sul caricamento eventi, che prima falliva
in silenzio mostrando una pagina vuota senza alcun messaggio.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 08:59:10 +02:00
Ivan CacciariandCursor a1d2599c47 Rinomina il link evento in Dettagli.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 08:54:03 +02:00
Ivan CacciariandCursor ca1eee5425 Ripulisce profilo e tab, e mette Apri evento sull'ultima riga delle presenze.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 08:48:21 +02:00
Ivan CacciariandCursor 6bc5e8f006 Ingrandisce i caratteri delle tab sottosezioni e dei controlli calendario.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 08:24:59 +02:00
davideandClaude Opus 5 f149d02e9a Apre il voto MVP due ore dopo l'inizio e lo riserva ai presenti.
La votazione non dipendeva dal tempo ma dal risultato: compariva solo con un
referto CSI o uno scout salvato, e chiunque avesse un profilo poteva votare.
Ora `votoMvpAperto()` la apre due ore dopo `data`+`ora` dell'evento, il pannello
sta in una sezione sua e votano — e sono votabili — solo i presenti (o in
ritardo) di quell'evento.

I voti passano dall'id scout/CSI a quello dell'evento CrAPP: home e classifica
leggono il vincitore tramite la mappa data → evento che avevano già. Le regole
sono applicative, non RLS: chi scrive su PostgREST le aggira, come già annotato
tra i limiti noti del modulo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 22:11:26 +02:00
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
Ivan CacciariandCursor 3d8cb22235 Compatta la classifica interna Squadra e allinea le tab a sottolineatura.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-06 18:50:11 +02:00
davideandClaude Opus 5 2b8ba340a6 Fa scadere i promemoria in coda e smette di dare la colpa alle iscrizioni.
Provando il promemoria palloni in produzione la notifica risultava inviata ma
non arrivava. La causa non era il codice: l'iscrizione di destinazione era
scaduta, FCM l'ha accettata con 2xx e ha buttato via il messaggio, e un minuto
dopo il dispositivo si è re-iscritto con un endpoint nuovo. Un 2xx dal server
push non significa consegnato, e non manda il 404/410 che farebbe pulire
push_subscriptions: è annotato fra i limiti noti, perché dal server non è
distinguibile.

Il difetto vero l'ha fatto emergere quella caccia. promemoria_push si svuota
solo quando il dispositivo legge il messaggio, quindi se la push non arriva mai
la riga resta per sempre — e push-messaggio serve la coda con priorità sul testo
calcolato. In produzione ce n'erano dieci, la più vecchia del 2 settembre: alla
notifica successiva, di qualunque tipo, quel telefono avrebbe mostrato un
sollecito presenze per un evento già passato.

Ora un promemoria vale 12 ore. La riga si cancella comunque alla prima lettura,
scaduta o no: cancellare solo le fresche lascerebbe le vecchie in coda a
dirottare ogni notifica futura, cioè il bug. Così la coda si smaltisce da sola e
le righe orfane già in produzione non vanno ripulite a mano.

Il messaggio del pulsante distingue infine i due casi che prima confondeva:
nessuna iscrizione fra gli incaricati, oppure iscrizioni presenti e invio non
riuscito. Il primo è informativo, il secondo è un errore — dirlo sbagliato
manda a cercare il problema dalla parte opposta.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:25:21 +02:00
davideandClaude Opus 5 ee9f3f1b8c Il promemoria palloni lo manda un admin dalla pagina evento (DD-025).
La route era disegnata per un cron quotidiano: calcolava chi è di turno oggi e
gli mandava una push. Ma nel repository nessun cron esiste, e palloni.md lo
annotava già come "da verificare lato hosting": nei fatti quel promemoria non è
mai partito. Il segreto condiviso introdotto ieri proteggeva una porta che
nessuno apriva, al prezzo di una variabile d'ambiente da configurare ovunque.

Ora la fa partire un amministratore dal pulsante "Avvisa chi è di turno", dentro
il riquadro palloni dell'evento. La route accetta un eventoId e avvisa i
destinatari di quell'evento invece della giornata corrente: chi deve prendere i
palloni e chi deve riportarli, con un testo diverso per ciascuno. Stesso
precedente di apri-sondaggio, manuale fin dalla v1.0.6.

Il testo va in coda su promemoria_push prima dell'invio. Serve: la push parte
vuota e il service worker chiede a push-messaggio cosa mostrare, ma quella route
sa raccontare solo la giornata corrente, quindi un avviso mandato il martedì per
il sabato arriverebbe con il testo generico.

avvisiPalloniEvento() è la nuova funzione pura, con i suoi test: due destinatari
con testi diversi, un avviso solo quando sono la stessa persona, niente avvisi
per un evento inesistente o senza turni. Un'asserzione verifica che il testo non
dica mai "oggi", perché può arrivare giorni prima.

richiediSegreto e CRON_SEGRETO spariscono: tutte e tre le route di notifica
usano ora richiediAdmin, e non resta nessuna variabile da configurare.
destinatariPromemoriaPalloni() resta in palloni-core.ts con i suoi test perché
push-messaggio continua a usarla per il testo calcolato al volo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:15:39 +02:00
davideandClaude Opus 5 847972b582 Chiede le credenziali alle route che avvisano tutta la squadra (DD-024).
Le route in src/routes/api/public/ girano con la service role e saltano la RLS,
quindi DD-023 non le copre. Nessuna faceva un controllo di accesso: cercando
"authorization" in quella cartella l'unico header era lo User-Agent con cui
csi.ts chiama il portale CSI. Chiunque conoscesse l'URL poteva far suonare i
telefoni della squadra, e promemoria-palloni accetta perfino una POST con il
corpo vuoto.

La difesa apparente delle altre due — serve un id evento valido — non è una
difesa: l'id è "e" più il timestamp in base 36, compare negli URL che la squadra
si scambia ed è elencabile da qualsiasi utente loggato.

auth-route.server.ts porta i due controlli, diversi perché i chiamanti sono
diversi. apri-sondaggio e sollecita-presenze usano richiediAdmin: token della
sessione verificato con auth.getUser, poi ruolo admin da user_roles, la stessa
fonte di ruoli.ts. Il controllo precede la validazione dell'input, così la
risposta non rivela nemmeno se un evento esiste. promemoria-palloni usa
richiediSegreto, perché la chiama un cron che una sessione non ce l'ha: se
CRON_SEGRETO non è configurata la route resta chiusa con 503, perché una porta
che si riapre da sola quando manca una variabile non se ne accorge nessuno.

csi, push-config, push-subscribe e push-messaggio restano aperte: le chiamano il
browser prima del login e il service worker, dove qualsiasi segreto finirebbe
nel bundle.

Lato client i due pulsanti admin mandano il token con intestazioniAutenticate(),
letto al momento della chiamata e non da uno stato React.

permessi-route.test.ts copre il giro intero — nessun token, giocatore, admin —
avviando il server di sviluppo puntato al database locale, perché servono utenti
veri. Il controllo positivo è il 404: l'admin supera l'accesso e arriva alla
validazione. In api.test.ts restano i rifiuti che non richiedono un utente e
sparisce la verifica della validazione di sollecita-presenze, che ora sta dietro
all'accesso.

I limiti noti di palloni.md sono aggiornati: il secret che il piano originale
prevedeva ora c'è. Resta vero che nessun cron chiama la route, quindi il
promemoria quotidiano non parte da solo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:05:39 +02:00
davideandClaude Sonnet 5 50a712c276 Allinea l'altezza di input, select e date nei form a un'unica misura.
input[type="date"] resta più alto di un input di testo anche con
appearance-none: h-10 esplicito su classiInput allinea tutti i campi.
La textarea note usa h-auto per non perdere le due righe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 11:07:15 +02:00
davideandClaude Sonnet 5 77f587b53b Uniforma l'altezza dei <select> agli altri campi dei form su Android.
Su Android il controllo nativo di <select> ignora parte del padding di
classiInput e risultava più alto o più basso degli input accanto.
appearance-none lo riporta a una scatola CSS normale; la freccia va
ridisegnata a mano perché appearance-none la fa sparire.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 10:57:05 +02:00
davideandClaude Sonnet 5 208dd5f2f6 Blocca la modifica delle presenze e il sollecito per eventi già passati.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 10:27:12 +02:00
davideandClaude Opus 5 0c136f8f53 Apre lo Scout Live dalla pagina partita e a tutta la squadra.
Tolto dalla home e poi da Squadra, `ScoutEntry` era rimasto orfano:
`/scout` si raggiungeva solo scrivendo l'URL a mano. Ora la card sta
nella sezione «Scout live» di `/partita/$id`. La prop `eventoId` la
accende solo se la partita aperta è quella di oggi: senza, da una
partita futura o passata si sarebbe finiti sullo scout di un'altra.

Cade anche la riserva agli admin, in `ScoutEntry` e nella route: può
scoutare chiunque sia autenticato, uno per volta grazie al lock di
sessione. È anche l'unico controllo che c'era, visto che le policy RLS
sono sempre state aperte a tutti gli autenticati.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:24:39 +02:00
davideandClaude Opus 5 73902cc0a4 Apre il sondaggio pre-partita alle 8:00 con avviso push manuale.
Il sondaggio cacche era votabile in qualsiasi momento, anche settimane
prima della partita. Ora `sondaggioAperto()` lo sblocca alle 8:00 del
giorno della partita e prima la card mostra solo l'avviso di apertura.

Aggiunge la route `POST /api/public/apri-sondaggio` e, per gli
amministratori, il pulsante «Avvisa tutti del sondaggio» nella card:
manda la push a tutti i dispositivi iscritti, con lo stesso meccanismo
del sollecito presenze. Nessun invio automatico.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:08:58 +02:00
davideandClaude Opus 5 2a1a0839c0 Estende alla card degli eventi il fix delle date su iOS.
Il `min-w-0` di 366928b era finito solo in `ProfiloAmministrativo` perché
`Campo` e le classi degli input erano ricopiati identici in tre file: il
form «Nuovo evento» e la dashboard admin («Data tessera» in `grid-cols-2`)
sfondavano ancora la card. Ora `Campo` e `classiInput` stanno una volta
sola in `ui-bits` e le tre copie spariscono.

La regola CSS che rende ridimensionabili i controlli nativi copre anche
`input[type="time"]`, che nel form eventi sta affiancato alla data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:41:20 +02:00
davideandClaude Opus 5 107407b854 Rinomina le tab del profilo in Documenti e Opzioni.
«Tesseramento» nominava lo scopo invece del contenuto (dati personali,
documento, certificato medico, foto tessera); cambia anche la rotta in
/profilo?tab=documenti. «Impostazioni» diventa «Opzioni» così le quattro
etichette stanno in uno schermo da telefono: le voci della barra si
dividono la riga in parti uguali (grow basis-0) e tornano a scorrere solo
se non ci stanno, quindi vale anche per Squadra e Classifica.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:37:41 +02:00
davideandClaude Opus 5 366928b61f Impedisce ai campi data di sfondare la card del tesseramento su iOS.
In `grid-cols-2` l'elemento di griglia è la label di `Campo`, che ha
`min-width: auto` e quindi non scende sotto la larghezza del contenuto. Su iOS
`input[type="date"]` è un controllo nativo con una larghezza intrinseca che
`width: 100%` non riduce: le due date affiancate sfondavano la card e
l'`overflow-hidden` del pannello a tab tagliava via quello che usciva.

`min-w-0` sulla label e sulla classe condivisa degli input, più `min-width: 0`
e `appearance: none` sulle sole date — è `appearance` a rendere il controllo
davvero ridimensionabile, e restando sulle date il select tiene la sua freccia.

Il fix sta in `CampiProfilo`, quindi copre anche la dashboard amministratore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:22:49 +02:00
davideandClaude Opus 5 84c4c0fc25 Revert "Corregge il rendering sfasato del tesseramento su iOS."
Annulla a5e627a: la diagnosi era sbagliata. I bordi sfalsati nella foto erano
distorsione prospettica dello scatto, non un artefatto di composizione, e il
sintomo vero era un altro (le card escono dallo spazio). Né il will-change del
pannello né il backdrop-blur della barra tab c'entravano.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:22:39 +02:00
davideandClaude Opus 5 a5e627a57b Corregge il rendering sfasato del tesseramento su iOS.
Il pannello delle sottosezioni è trascinabile: Motion gli lasciava
`will-change: transform` in permanenza e su iOS il sottoalbero finiva in un
layer composito. Sul tab Tesseramento, il più alto, il layer supera il tile di
WebKit e il contenuto veniva ridipinto a fasce sfalsate.

Disattiva la gestione automatica del will-change e toglie il backdrop-blur
dalla barra tab: non è sticky, quindi non sfocava nulla, ma il backdrop root
forzava il rendering a layer degli antenati.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:12:20 +02:00
Ivan CacciariandCursor 033c45df4d Apre Tesseramento dal widget Completa profilo in home.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 22:38:48 +02:00
Ivan CacciariandCursor ce1c7cfd4b Alleggerisce il cambio tab delle sottosezioni togliendo exit e molle.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 22:24:57 +02:00
Ivan CacciariandCursor e9755d364c Aggiunge sottosezioni a swipe nel profilo, con stagione e serie unite.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 22:07:58 +02:00
Ivan CacciariandCursor ede02b7575 Aggiunge sottosezioni a swipe su Squadra e Classifica.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 21:18:19 +02:00
davideandClaude Opus 5 c089d9e8ba Dà profondità alle superfici e trasforma la barra in vetro
Fondo pagina, carte e `secondary` stavano dentro tre punti di luminosità:
carta e fondo avevano un rapporto di contrasto di 1.04, cioè erano lo stesso
bianco. Le carte non si leggevano come carte, e l'unica cosa che le staccava
era `shadow-card` — 6% di nero, che su un telefono al sole non esiste. Da qui
l'impressione che fondo, barra ed elementi fossero «tre bianchi».

La rampa dei neutri è ridistanziata: fondo 0.93, carta 1.0, `secondary` 0.90.
Carta e fondo passano a 1.23, un elemento dentro una carta sta a 1.35 dalla
carta ed è più scuro di essa, come nelle liste raggruppate di iOS. 0.93 è il
fondo più scuro possibile senza toccare altro: il limite lo impone
`text-accent` appoggiato sul fondo, che a 0.92 scenderebbe sotto 4.5.

Due colori vanno corretti di conseguenza, altrimenti la modifica peggiora la
leggibilità: l'accento scende a 0.54 (a 0.58 come testo faceva 4.07 sul nuovo
fondo; ora fa 4.62, e migliora anche il bianco che ci sta sopra, da 4.65 a
5.51) e `muted-foreground` a 0.48. Verificati convertendo le oklch in sRGB e
calcolando i rapporti, non a occhio.

`--destructive` e `--ring` erano *lo stesso identico colore* dell'accento:
«elimina», «selezionato» e «ho il focus qui» davano il medesimo segnale. Il
distruttivo resta rosso ma più scuro e cupo, il focus viene dal neutro.

Le ombre passano da una a tre quote: `shadow-card` per il contenuto,
`shadow-chrome` per barra, drawer e toast (più profonda, più il filo chiaro sul
bordo alto), `shadow-alza` neutra per l'hover — `premi` metteva un alone rosso
sotto ogni riga di classifica.

La barra ora usa `vetro`: fondo al 62% invece dell'85%, così ci si vede
attraverso, più tre ombre interne che sono il bordo illuminato. L'85% di
`materiale` esiste per tenere allineati WebKit e Blink (608f2ed) su una fascia
larga quanto lo schermo; su una pillola di 60 px lo scarto non si legge.

Dietro `@supports`, un `feDisplacementMap` distorce davvero il fondale: solo
Blink accetta un filtro SVG in `backdrop-filter`, e WebKit ignorando l'url
scarterebbe anche il blur. Miglioria progressiva, non l'effetto principale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 20:02:13 +02:00
davideandClaude Opus 5 bb4fb624f5 Mette lo stemma della squadra nell'intestazione del profilo
In alto a destra sul profilo c'era `/icon-192.png`, cioè l'icona della PWA: ha
dentro il nome dell'app, e come marchio in una schermata dell'app stessa non
dice niente. Lì serve lo stemma della squadra.

`TeamLogo` prende una prop `src` con l'icona come default, così cambia solo la
chiamata del profilo. L'icona della PWA resta quella dappertutto altrove
(ripiego di `LinkProfilo`, schermata di caricamento, manifest, apple-touch-icon):
sulla home dello schermo è giusto che l'app si presenti come «CrAPP».

Il file arrivava come `logo_nerorosso.jpg` ma è un SVG: rinominato con
l'estensione giusta, altrimenti verrebbe servito come JPEG e non si vedrebbe.
Sta in `public/`, che è la cartella servita così com'è.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 19:35:44 +02:00