Vai al contenuto
SIMONE PIZZI
Progetti ↗
Runtime Radio Web Site

Runtime Radio: alle tre di notte il sito diceva «In Diretta»

Dalla v2.9.10 alla v2.28.0: tre mesi, quarantacinque versioni. La Live Room, i ruoli della redazione, 567 prove automatiche, e un mucchio di piccole bugie scoperte misurando invece che ricordando.

Data
Lettura
14 minuti
Sezioni
11
Copertina: Runtime Radio: alle tre di notte il sito diceva «In Diretta»

Alle tre di notte, con la radio in rotazione automatica e lo studio vuoto, la barra laterale di runtimeradio.it continuava a mostrare la scritta «In Diretta». Lo faceva anche l'hero in cima alla pagina, mentre il player teneva acceso il suo bollino rosso.

Non si trattava di un bug recente. Quel bollino era nato acceso assieme al player, quando la pagina dedicata alla diretta non esisteva ancora e l'etichetta «live» indicava semplicemente che lo streaming audio era attivo. Quando poi la pagina della diretta è stata creata, il termine ha assunto un significato diverso, ma il vecchio indicatore è rimasto invariato: acceso ventiquattr'ore su ventiquattro, per mesi.

Questa non è la criticità più seria emersa negli ultimi tre mesi, ma illustra bene il problema di fondo: il sito conteneva diversi comportamenti e stati che nessuno si era mai preoccupato di verificare. Non per trascuratezza deliberata, ma per la presenza di logiche rimaste attive mentre il contesto attorno evolveva. Il lavoro principale dell'estate non è consistito tanto nell'introdurre nuove funzionalità, quanto nel dare all'infrastruttura gli strumenti necessari per smentire le proprie assunzioni.

A maggio avevo raccontato il riallineamento di un'architettura rimasta indietro nel tempo: venti release in quindici giorni, 16 vulnerabilità corrette, il redesign con la skin Stratum, la migrazione da Quill a Tiptap. Concludevo sottolineando che la piattaforma «suona, e adesso è anche sicuro, coerente e mantenibile».

Da quel momento sono seguite altre quarantacinque versioni, passando dalla v2.9.10 alla v2.28.0.


Il Muro di Runtime

Parto dalle modifiche all'interfaccia visibile.

La pagina /live non si limita più a ospitare lo streaming. Sotto il player adesso si trova il Muro: gli ultimi dieci messaggi inviati compaiono come note manoscritte, disposte con una leggera rotazione angolare e la firma dell'autore. Il tema usa verde su verde, inchiostro chiaro, una puntina arancione in testa e la firma in tonalità menta, con il font Caveat a simulare la scrittura a mano.

Ci sono due scelte implementative specifiche: la rotazione di ciascun biglietto è deterministica, derivata dall'ID del record nel database (evitando che a ogni polling di aggiornamento le note cambino inclinazione a caso), mentre la griglia adotta un layout a colonne indipendenti, scongiurando sovrapposizioni e troncamenti di testo a prescindere dalla lunghezza del messaggio.

Il Muro è aperto a tutti e rimane attivo continuamente, anche a regia spenta: i messaggi e le dediche fungono da bacheca generale della stazione. Le richieste musicali rimangono invece vincolate alla presenza di una trasmissione dal vivo, gestite da una coda che ha senso solo in presenza di uno speaker in onda.

A fianco si trovano i sondaggi, rifiniti a partire dalla v2.11.12: le opzioni compaiono a cascata, l'avanzamento delle percentuali è sincronizzato con i contatori numerici, e alla chiusura dei voti l'opzione vincente riceve una corona dorata con un effetto alone a respiro. L'intera animazione è realizzata in puro CSS e requestAnimationFrame, senza dipendenze esterne e con il blocco completo dei movimenti sotto la direttiva prefers-reduced-motion.

È stato integrato anche il torneo a punti, con sistema di graduatoria denso: l'eventuale parità al primo posto non cancella l'assegnazione del secondo.

Sondaggi e tornei sono stati collaudati direttamente durante una trasmissione in onda, completando le verifiche funzionali rimaste in sospeso.


Da amministratore unico a redazione

Fino a luglio il pannello di controllo prevedeva due soli profili: admin, con accesso illimitato, ed editor, limitato alla stesura delle news e a poche altre voci a causa dei vincoli sugli endpoint dell'API.

A partire dalla v2.15.0 ogni utente riceve un set granulare di permessi, e i controller controllano la specifica autorizzazione anziché il ruolo complessivo: editor per la gestione contenuti, podcaster per show e feed dedicati, regia per il solo modulo Live. L'utente che accede come podcaster visualizza quattro voci nella barra laterale e il client evita a monte di interrogare le route non autorizzate.

Questa modifica risolve un problema concreto: in precedenza un editor che accedeva alla dashboard riceveva risposte HTTP 401 dagli endpoint non di sua competenza, e l'interceptor delle richieste interpretava l'errore mostrando a schermo un falso avviso di «sessione scaduta».

All'interno dello stesso blocco di modifiche è stato introdotto il logout globale: la variazione o il ripristino della password invalida all'istante tutte le sessioni generate in precedenza. L'intercettazione è centralizzata nel layer di connessione al database, punto di passaggio obbligato di tutte le chiamate autenticate.


Contare senza schedare

La piattaforma integra un motore proprietario per il tracciamento delle visite, sviluppato in PHP e MySQL per evitare dipendenze da piattaforme terze. Il vincolo tecnico iniziale derivava dall'ambiente di hosting condiviso, privo di runtime Node; l'obiettivo primario era però la tutela della privacy.

Il sistema non raccoglie dati personali identificativi. Non impiega cookie né identificatori persistenti. Ogni visitatore corrisponde a un'impronta generata tramite un salt crittografico che ruota quotidianamente, con eliminazione automatica delle chiavi precedenti: gli indirizzi IP non vengono mai registrati e accessi effettuati dallo stesso dispositivo in giorni differenti producono impronte slegate. Il tracciamento memorizza il percorso della richiesta scartando la query string, e registra del referrer soltanto il dominio di provenienza.

Trascorsi sessanta giorni, i dettagli delle singole visite vengono rimossi mantenendo solo le aggregazioni numeriche giornaliere. I task di pulizia si avviano automaticamente all'apertura del pannello, senza richiedere cron dedicati.

La schermata di Panoramica mostra i dati relativi alla giornata corrente, a 7 e a 30 giorni, con i grafici delle visualizzazioni, l'elenco degli articoli più letti e i domini di provenienza, sostituendo il vecchio testo segnaposto che segnalava l'assenza di rilevamento.

Nel Magazine sono state aggiunte la funzione di ricerca e le reazioni rapide (👍 ❤️ 🔥 👏) in calce ai contenuti. La scelta di non inserire i commenti tradizionali risponde alla stessa logica: eliminare la necessità di moderazione e lo stoccaggio di dati utente, limitando l'elaborazione ai soli contatori numerici sul server.


Nove ore di scarto

L'11 agosto avevo annotato un quesito in roadmap: su DreamHost, PHP e MySQL condividono lo stesso orario?

Le verifiche dirette sul server hanno evidenziato una discrepanza di nove ore:

codeCode

sistema:      2026-08-11 00:38 PDT
PHP del sito: 2026-08-11 09:38 CEST
MySQL:        2026-08-11 00:38

Il server fisico si trova nel fuso orario del Pacifico. Un file di bootstrap imposta l'interprete PHP sul fuso di Roma, ma il server MySQL continuava a girare sull'orario locale della macchina. Ogni operazione che confrontava i due orologi produceva un errore logico.

Nello specifico:

  1. Il sistema delle reazioni validava gli articoli pubblicati confrontando la data generata da PHP con la funzione NOW() di MySQL: di conseguenza, per le prime nove ore dalla pubblicazione l'API restituiva un errore 404 all'invio di una reazione.

  2. L'antispam del Muro, impostato su una soglia di dieci secondi tra messaggi consecutivi dal medesimo client, confrontava il timestamp di PHP con quello salvato da MySQL. La differenza risultava sempre di circa nove ore: il filtro non è mai entrato in funzione.

  3. Il blocco di sicurezza di due minuti per l'iscrizione alla newsletter presentava la stessa anomalia.

  4. Il reset giornaliero delle statistiche si verificava a mezzanotte PDT, corrispondente alle 9:00 del mattino italiane.

La correzione ha richiesto una sola riga di configurazione: all'apertura della connessione verso il database viene impostato l'offset orario corrente di PHP. Il valore viene ricalcolato a ogni sessione per gestire i cambi tra ora solare e legale, e usa l'offset numerico per aggirare l'assenza delle tabelle dei fusi orari spesso riscontrata sugli ambienti condivisi.

La validazione è stata condotta configurando il database locale di test a −07:00 (replicando DreamHost) ed eseguendo i test automatici: con il parametro di sincronizzazione attivo la suite si chiude con successo, mentre disattivandolo falliscono le tre asserzioni legate ai componenti descritti.

Ne consegue un disallineamento nei dati storici: le righe registrate prima della modifica riportano i timestamp in formato PDT, quelle successive in CEST. Lo scalino temporale è rimasto invariato, poiché tali record sono impiegati esclusivamente per aggregazioni giornaliere o controlli a finestra stretta.


Mettere il sito nella condizione di smentirmi

A maggio il backend non disponeva di test automatici: i controlli erano limitati a verifiche manuali da browser.

Attualmente la suite include 567 asserzioni PHP e 71 test lato frontend, eseguiti a ogni push nel repository.

Gli endpoint dell'applicazione inizializzano la connessione al database in modo autonomo. Per testarli senza inserire backdoor nel codice di produzione, la prima iterazione della test suite sfruttava la reflection per iniettare un'istanza SQLite nella classe di gestione prima della chiamata all'endpoint.

Questo approccio copriva la logica di base, ma non le query scritte con sintassi specifica per MySQL (come le procedure di autenticazione, la Live Room, la newsletter, le reazioni e il modulo analytics). Invece di riadattare SQLite per emulare la sintassi MySQL, la pipeline di test è stata aggiornata per eseguire un'istanza MySQL reale, lanciando la suite su entrambi i motori.

Durante l'esecuzione con MySQL, l'endpoint viene instanziato all'interno di una copia temporanea isolata dell'albero API, con configurazioni puntate al database di collaudo. Questa separazione assicura che cache e backup generati dai test restino circoscritti all'ambiente temporaneo ed esclude il rischio di eseguire per errore operazioni distruttive sul database di produzione.

Tra le anomalie intercettate dalla suite:

  • I cinque controller dedicati alla gestione account contenevano controlli di accesso legacy: in caso di sessione scaduta leggevano una chiave non inizializzata e rispondevano con un codice HTTP 403 anziché 401. Di conseguenza, l'amministratore vedeva comparire a schermo l'errore «Forbidden: Admins only» invece del redirect al login.

  • Il meccanismo di logout globale non era coperto da test a causa della logica interna alla classe di connessione. Ora il flusso viene verificato simulando due accessi contemporanei, aggiornando le credenziali da una sessione e validando la revoca immediata della seconda.


Le icone della casa

Il set di icone del sito è stato ridisegnato internamente, abbandonando le librerie generiche.

Il progetto includeva in totale 105 icone attive. Di queste, 65 appartengono all'area pubblica, mentre 40 sono impiegate esclusivamente nella console di amministrazione, dove si è scelto di mantenere il set standard per ragioni di coerenza gestionale.

Le 65 icone dell'interfaccia utente sono state uniformate secondo specifiche fisse:

  • Griglia di base a 24px con area attiva a 20px, per bilanciare il peso ottico complessivo.

  • Spessore del tratto a 1.5px anziché 2px, allineato alle linee sottili e ai font monospazio del layout.

  • Terminali retti e angoli vivi, coerenti con l'identità grafica del sito.

  • Coordinate bloccate su valori interi per prevenire sbavature di rendering.

  • Elementi pieni ove necessario, per evitare dispersioni visive sui tratti fini.

I cinque loghi social conservano le geometrie originali dei rispettivi brand, adattando solo spessori e raccordi.

Mantenendo inalterata l'interfaccia dei componenti, l'aggiornamento ha richiesto la sola modifica delle direttive di importazione su 29 file.


Tailwind 4, e una classe morta da un anno

Il passaggio a Tailwind 4 comportava rischi legati a possibili regressioni visive.

La migrazione è stata controllata tramite uno script di confronto che ha campionato 33 proprietà CSS calcolate sui primi 400 nodi DOM di cinque pagine campione. Escluse le variazioni sintattiche, sono emerse due differenze sostanziali:

  1. Nell'hero della home era presente la classe gap-15, introdotta a maggio. La scala predefinita di Tailwind 3 non supportava il valore 15, rendendo la regola inefficace; con il parser dinamico di Tailwind 4 la classe è stata compilata come spaziatura da 60px, stringendo la larghezza della colonna del titolo. La classe è stata rimossa.

  2. Un valore di interlinea non interpretato correttamente da Tailwind 3 è stato agganciato correttamente dalla nuova versione, recuperando una riga di testo nel layout.


La hero che ha smesso di mentire

Tornando alla gestione degli stati del player: hero, sidebar e player leggono ora lo stesso payload restituito da AzuraCast, gestendo tre stati distinti:

  • Rotazione automatica: etichetta «In Streaming» con indicatore arancione a pulsazione lenta.

  • Speaker in trasmissione: etichetta «In Diretta» con indicatore rosso a frequenza rapida.

  • Podcast: badge nascosto.

L'altezza della hero è stata ridotta dall'82% al 54% dell'altezza del viewport, ridimensionando copertina e titoli, con il testo che alterna bianco e arancione.

Il claim dell'emittente («Trasmettiamo dal 2010, 24 ore al giorno, senza padroni») è stato spostato nell'area footer, liberando la sezione del player multimediale.

La transizione grafica al cambio di traccia è stata definita prototipando tre varianti di animazione in pagina isolata: l'implementazione finale unisce la gestione copertina della prima proposta e il testo della terza. La copertina subentra con una transizione in nove step ed effetto scanline; il titolo compare con scorrimento a cascata da destra, caratteri sfocati che si ricompongono con un ritardo di 22ms e spaziatura tra le parole; il nome dell'artista include un cursore lampeggiante in stile terminale.

L'intero blocco è gestito in CSS senza dipendenze. Le proprietà animate sono dichiarate visibili di default: l'esecuzione degli effetti si attiva solo in assenza della direttiva prefers-reduced-motion, garantendo una visualizzazione statica immediata per gli utenti che disabilitano le animazioni.


Quello che c’è di nuovo in home

Due ulteriori interventi completano l'aggiornamento dell'home page:

Ultimi episodi aggregati al posto dei contenuti in evidenza. La lista statica di sei podcast è stata sostituita da un elenco dinamico delle ultime uscite del network. Poiché il catalogo conta circa 45 feed RSS, l'aggregazione viene calcolata lato server per evitare 45 chiamate HTTP per ogni visitatore. Il sistema implementa un doppio livello di cache con logica di fallback su dato scaduto in caso di timeout del database o delle sorgenti esterne. Il recupero dei feed avviene in parallelo: l'eventuale errore su una sorgente non compromette l'elaborazione dei feed rimanenti.

Banner promozionali sotto la hero. Sei riquadri con rotazione automatica ogni otto secondi: FAVELLA 1 con la relativa documentazione, Titan Edition (rilasciato come software open source ad agosto), il mio sito personale, The Mystery of the Santa Maria e L’Albero dei Racconti.

I banner sono stati integrati direttamente nel markup in formato SVG nativo, senza ricorrere a immagini raster (che pesavano complessivamente circa 1 MB). Questa scelta azzera l'impatto sul payload e preserva i riferimenti cromatici dei singoli progetti: il ciano e l'ambra di Favella, il blu di Titan, il verde fosforo della Santa Maria e i toni scuri dell'Albero.

I testi vettoriali sono stati ricalibrati sulle dimensioni effettive di rendering dei singoli SVG per impedire lo straripamento dei caratteri dai contenitori grafici.


Le pulizie

Sul fronte dell'amministrazione server sono stati eseguiti tre interventi:

  1. Bonifica degli asset obsoleti: la directory statica conteneva 1.729 file accumulati da febbraio per via di deploy incrementali senza cancellazione. Il filtro di pulizia ha escluso i file della build corrente, le risorse create da meno di sette giorni e gli asset referenziati nel bundle attivo, portando alla cancellazione di 1.504 file non più utilizzati (pari a 49 MB di spazio liberato).

  2. Rimozione script orfani: uno script di migrazione eliminato a giugno era stato reintrodotto con il deploy di luglio. La verifica ha evidenziato che la configurazione del web server restituiva un codice 403 sui percorsi protetti anche per file non esistenti, mentre il router client intercettava le richieste non mappate rispondendo con status 200. I codici di stato HTTP non costituiscono, da soli, una prova dell'effettiva presenza di un file su disco.

  3. Automazione dei backup: il dump del database è affidato a un cron job schedulato alle 4:00 (ora italiana), con credenziali caricate da file con permessi restrittivi, verifica dell'integrità dell'archivio generato e retention impostata a quattordici giorni. Il processo scrive a ogni esecuzione una riga di log con data, esito e dimensione del file, rimpiazzando il vecchio trigger manuale legato all'accesso al pannello.


Cosa resta

Gli elementi ancora aperti riguardano:

TypeScript 7: testato e momentaneamente congelato. Nonostante il compilatore nativo riduca i tempi di type-checking da 3,9s a 1,1s, le incompatibilità con l'infrastruttura di linting richiedevano configurazioni ridondanti per un risparmio di tre secondi. La issue a monte risulta tuttora aperta al 18 agosto.

Eccezioni del linter: ridotte da diciannove a sei, ciascuna corredata da motivazione in linea. Le tredici regole rimosse sono state aggregate all'interno di due helper centralizzati. Tra le sei eccezioni mantenute figura il blocco della graduatoria del torneo durante la fase di chiusura della diretta, non modificata per non introdurre rischi operativi sulla grafica in onda.

La pianificazione per una terza versione del sito è rimandata a fine anno.


A maggio il sito era sicuro, coerente e manutenibile; i mesi successivi sono serviti a garantire che ogni stato mostrato a schermo rifletta con precisione ciò che accade sui server e in trasmissione.


Runtime Radio trasmette dal 2010, ventiquattro ore al giorno. Il sito è su runtimeradio.it, la diretta su runtimeradio.it/live. Stack: React 19, TypeScript, Vite 8, Tailwind 4, PHP 8 e MySQL su hosting condiviso. Sviluppato da Simone Pizzi.


Ogni tanto scrivo una lettera

Nessuna cadenza fissa, nessun riassunto automatico: parte solo quando ho qualcosa da raccontare. In fondo a ognuna c'è il link per uscire.

↑ Su

Cerca nel sito

Condividi

L'indirizzo di questa pagina:

Scrivi a Simone

Segnalazioni, correzioni, o solo per dire che una cosa non funziona: quest'ultima è la più utile di tutte.