Commit Graph
24 Commits
Author SHA1 Message Date
davideandClaude Opus 5 d8f334007b Fa arrivare le push ad app chiusa e rende diagnosticabile quando non arrivano.
Il testo della notifica viaggia ora cifrato dentro la push (aes128gcm, RFC
8188/8291) invece di essere recuperato dal service worker con una fetch al
risveglio. Era quella fetch a non chiudersi in tempo: ad app chiusa il browser
tiene vivo il worker pochi secondi, showNotification non veniva mai chiamata e
non compariva niente, mentre ad app aperta con la rete calda sembrava tutto a
posto. La POST porta anche Urgency: high, che chiede la consegna immediata
invece di far accumulare i messaggi fino al risveglio del dispositivo.

Cadono i pezzi che esistevano solo per rimediare al payload vuoto: la route
push-messaggio, la coda promemoria_push con la sua scadenza a 12 ore,
messaggioPalloniOggi() e il timeout nel worker. Tutti e tre i mittenti avevano
gia il testo pronto prima di inviare.

Il worker si aggiorna da solo all'avvio e a ogni ritorno in primo piano
(mantieniWorkerPushAggiornato): nella webapp installata quello vecchio puo
sopravvivere a lungo, e senza questo un dispositivo resterebbe fermo alla
versione che va a cercare il testo in rete.

Profilo -> Opzioni ha "Mandami una notifica di prova", visibile solo a notifiche
attive: manda una push a questo dispositivo e riporta stato HTTP, corpo della
risposta e se l'endpoint risulta davvero in push_subscriptions. Senza, "non
arriva" era cieco: ogni prova richiedeva un admin, un evento nello stato giusto
e una seconda persona, e la risposta del servizio push veniva buttata via.
inviaPush torna { stato, corpo } e logga il corpo sui rifiuti.

Dalle prove sul campo: a parita di server, iPhone installato da Home riceve ad
app chiusa. Su Android installato come webapp resta da verificare: il WebAPK e
un'app Android a se, con permesso notifiche (Android 13+) e voce batteria
distinti da quelli di Chrome. Annotato nei limiti noti.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 14:24:07 +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 0addafa76f Separa il calcolo delle presenze del mese dagli hook e lo copre con i test.
La percentuale di presenze degli ultimi 30 giorni decide le convocazioni, ma
stava dentro due useMemo — scritta due volte, una per giocatore e una per la
rosa — e nessun test la toccava. Le tre regole che la determinano non sono
ovvie: la finestra è di 30 giorni, il ritardo conta come presenza, e convocati
vuoto significa tutta la rosa attiva.

presenze-mese-core.ts contiene ora il calcolo puro, presenze-mese.ts solo il
collegamento agli hook. Comportamento invariato, inclusa la differenza fra i due
percorsi: per giocatore si filtrano i convocati, per la rosa l'elenco vuoto si
espande agli attivi.

Il test fissa le tre regole e i casi che le rompono: nessun evento nella
finestra dà 0 e non NaN, chi non è convocato resta fuori dal denominatore, chi
non ha mai risposto ci resta dentro.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 11:41:14 +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
Ivan CacciariandCursor 61c6aaaaed Toglie la proposta automatica dei palloni sugli allenamenti.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 22:44:25 +02:00
davideandClaude Opus 5 1ff650ecd5 Aggiunge i test della proiezione del momento
`proietta()` decide dove atterra uno swipe, quindi è la funzione che rompe il
gesto se sbaglia: il test copre segno, simmetria, linearità nella velocità e
il valore atteso della decelerazione esponenziale (1000 px/s -> ~499 px).
Copre anche i preset di `molla`, per evitare che un rimbalzo finisca per
sbaglio sul default o che una durata esca dalla scala utile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 12:42:11 +02:00
davideandClaude Opus 5 7237e8ff39 Calcola davvero le serie di presenze e sblocca badge e obiettivo.
I tre contatori (serieAllenamenti, seriePartite, serieConferme) e streak erano
zero fisso in useRosa(): la sezione Serie di presenze del profilo mostrava sempre
progresso nullo, i badge legati alla costanza erano impossibili da sbloccare e
l'obiettivo Continuita di squadra restava a 0/12.

serieConsecutiva() e serieConferme() (src/lib/presenze.ts) derivano le serie dagli
eventi passati e da risposte_presenze gia in cache, senza query aggiuntive.

Migration m9: nuova colonna risposto_il con l'istante della prima risposta, resa
immutabile da un trigger, confrontata con eventi_app.creato_il per le conferme
entro 24 ore. Serviva perche aggiornato_il registra l'ultima modifica, quindi chi
rispondeva subito e cambiava idea dopo risultava lento.

Corregge anche la barra di progresso, che misurava valore/prossimo e tornava
indietro a ogni traguardo raggiunto (2/3 = 67%, poi 3/6 = 50%).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:46:04 +02:00
davide 923d1fe762 Aggiorna i test per le nuove firme con rosa esplicita
completaTurni() e csvScoutMatch() prendono ora la rosa come parametro
invece di leggerla dalla lista statica di crapp-data.ts.
2026-09-03 10:17:07 +02:00
davideandClaude Sonnet 5 c7445cfab1 Aggiunge il tracciamento del tesseramento CSI in giocatori_squadra
Numero e data della tessera arrivano dal comitato dopo l'iscrizione, quindi
li scrive solo un admin (come numero/ruolo): estende il trigger di M1/M5,
aggiunge il pannello dedicato in /admin con badge e conteggio in dashboard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 15:27:12 +02:00
davideandClaude Sonnet 5 ff45b2379c Aggiunge unit test per i moduli lib rimasti scoperti
Copre la logica pura isolabile di avatar-store, error-capture, error-page,
lovable-error-reporting, profili (validazione upload), push-client (guardie
senza DOM), scout-stato e webpush.server (firma JWT VAPID con chiavi P-256
generate al volo, fetch intercettato). Alza i file di src/lib coperti da 19
a 28 su 37, senza introdurre nuove dipendenze.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 11:34:09 +02:00
davide d3b117e470 Ordina la rosa e la squadra per cognome
Sposta dividiNome in crapp-data.ts per riordinare la rosa di fallback per
cognome; la query a giocatori_squadra ora ordina per cognome, nome invece
che per id.
2026-09-02 10:59:28 +02:00
Ivan CacciariandCursor c52e4c4e27 Separate player presence from scout stats
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 22:42:35 +02:00
Ivan CacciariandCursor d63beae04c Replace demo stats with real data
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 22:34:07 +02:00
davideandClaude Sonnet 5 c314a08f6d Sincronizza lo Scout Live tra dispositivi (blocco e archivio partite)
Due bug architetturali, entrambi con la stessa causa: dati che vivevano
solo in localStorage, quindi visibili a un solo dispositivo.

- Il blocco "chi sta scoutando" (scout-live.ts) non funzionava mai tra
  telefoni diversi: due persone potevano prendere il controllo insieme
  da dispositivi diversi, sovrascrivendosi a vicenda le azioni nel
  salvataggio condiviso. Ora usa scout_sessioni, tabella già presente
  nello schema ma mai collegata al codice.

- La partita scoutata finita (scout-store.ts) veniva salvata solo in
  localStorage: "Punti/Ace/Muri squadra" e le presenze derivate dallo
  scout esistevano solo sul telefono di chi aveva chiuso la partita.
  Nuova tabella scout_partite archivia la partita completa (azioni
  incluse) per tutta la squadra.

useScoutMatches() mantiene la stessa firma di prima (ScoutMatch[]), ora
alimentata da una query invece che da localStorage: nessuna modifica
necessaria nei punti che la leggono (squadra, classifica, home,
dettaglio partita, rosa). Rigenerati i tipi Supabase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 16:08:59 +02:00
davideandClaude Sonnet 5 1a93c7657a Aggiorna i test dopo la rimozione della classifica e dello storico dummy
Tolti i check su `classifica`, `classificaConScout` e `storicoMatch`,
non più esportati da crapp-data.ts / scout-store.ts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 13:39:12 +02:00
davideandClaude Sonnet 5 d813dee282 Dashboard admin: aggiungi, disattiva e riattiva giocatori
- Aggiungi giocatore: nuovo form in /admin (nome, cognome, numero,
  ruolo, email opzionale), id g<N> calcolato in automatico.
- Email modificabile anche per i giocatori già in rosa dal pannello
  Dati squadra, non solo alla creazione — completa quanto rimandato
  da DD-018.
- Disattiva/Riattiva: un giocatore che lascia la squadra sparisce
  dalla rosa attiva senza che la riga venga eliminata, così presenze,
  voti, pagelle e badge della stagione restano agganciati al suo id.
  Nuova sezione "Giocatori disattivati" per riattivarli.
- Messaggio d'errore leggibile per l'unico vincolo unique della
  tabella (email duplicata) invece del codice Postgres grezzo.

Nessuna migration: sia l'inserimento sia la modifica passano dalla
policy admin FOR ALL già esistente su giocatori_squadra.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 11:29:41 +02:00
davideandClaude Mythos 215af4bbc4 DD-018: collegamento automatico giocatore-account per email
Al primo accesso l'app collega da sola l'account Google al giocatore
la cui email registrata coincide (case-insensitive), invece di far
scegliere il nome da un elenco. Senza corrispondenza compare solo un
messaggio d'errore con un pulsante per uscire e riprovare con un altro
account: nessuna scelta manuale di ripiego.

- Migration m5: colonna `email` su giocatori_squadra, seed per Ivan
  Cacciari e Davide Grilli, trigger esteso per richiedere anche il
  match email oltre allo slot libero.
- slotPerEmail() sostituisce slotLiberi() (rimossa, senza più
  chiamanti in produzione).

Co-Authored-By: Claude Mythos  <noreply@anthropic.com>
2026-09-01 10:46:54 +02:00
davideandClaude Sonnet 5 127e7c76e9 Copre giocatori-squadra e la scelta dei messaggi push palloni
Estrae in palloni-core.ts la logica pura di push-messaggio e
promemoria-palloni (finora mista alle chiamate Supabase), così da
poterla testare senza scrivere sul database. Aggiunge test per le
funzioni pure di giocatori-squadra.ts, finora senza copertura.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 09:39:09 +02:00
davideandClaude Opus 5 1036eb860d DD-011: make Google login the only way in
Remove the free player selection from /benvenuto: without a Supabase session
no screen renders, and the VITE_AUTH_OBBLIGATORIA bridge flag is gone.
Admin rights now come only from user_roles, so the hardcoded name list in
crapp-data.ts is deleted along with its tests.

Add migration m4_solo_autenticati, which revokes anon access to the v1.0
tables. Apply it only once the whole team has linked an account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 18:21:34 +02:00
davideandClaude Opus 5 53b2252997 Test sulla validazione dei dati squadra
Copre i controlli che evitano un errore Postgres di ritorno: nome, cognome e
ruolo non vuoti, numero di maglia intero e positivo (il CHECK della tabella), e
il rilevamento di un numero già assegnato a un altro giocatore attivo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 18:15:16 +02:00
davideandClaude Opus 5 255afde48e Test per profili, ruoli e permessi sul database
Copre le parti nuove e una trappola che sarebbe passata inosservata.

- unit: completamento del profilo (30/30/30/10), stato di scadenza dei
  documenti, export CSV, conversione riga <-> modello, anagrafica di squadra.
- unit: risoluzione dei permessi admin, incluso il fatto che con una sessione
  attiva decide il database e la lista di nomi non conta più.
- integration (schema-profili): verifica su un database vero le colonne che il
  codice legge, il bucket privato e la chiusura verso l'utente anonimo.
- e2e: /admin entra nell'elenco delle schermate verificate.

schema-profili tenta scritture da anonimo per dimostrare che la RLS le respinge,
e poi rilegge la riga: su un UPDATE che tocca zero righe PostgREST risponde 2xx,
quindi fidarsi del codice di risposta darebbe un falso verde. Si salta da solo
dove M2/M3 non sono ancora applicate, indicandolo nel motivo.

Verificato: 8/8 contro lo stack locale (che ha M2/M3), 21/21 file con
npm run test:all contro il progetto cloud.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 17:58:12 +02:00
davideandClaude Opus 5 9ca124a868 Expire a scout session whose timestamp cannot be read.
sessioneScaduta compared Date.now() against NaN when aggiornato_il was
not a valid date, and every comparison with NaN is false: the session
was reported as still active, so the Scout Live table stayed locked to a
player who could no longer release it.

An unreadable timestamp now frees the session, which is the safe
direction: at worst someone takes over a scouting session that was
already unattended.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 13:07:59 +02:00
davideandClaude Opus 5 af563603f9 Add a test suite for the domain logic and the server routes.
The project had no automated verification at all, so the "Test" step in
the AGENTS.md workflow rested entirely on clicking through the app.

The suite runs on bun with node:assert and adds no dependency: every file
is a script that exits non-zero when a check fails, and test/run.ts runs
each one in its own process. Unit tests cover the rules that decide what
players see (badges, streaks, ball duty rotation, ratings, MVP ties,
scouting totals, goals, notifications, CSI parsing); integration and
end-to-end tests drive the real dev server. Nothing writes to the
database, so both can be pointed at a live environment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 13:07:41 +02:00