← Blog
Note di lavoro

Da un feedback esterno al rilascio: due feature DFD su un prodotto in produzione

Un documento di feedback UX, due feature nate da lì e portate in produzione con DFD e un agente. Questa volta i gate hanno detto la verità, ma le evidenze vivevano su una sola macchina, i requisiti sono arrivati a lavoro iniziato e la specifica ha trovato un bug prima del codice.

Due finestre affiancate con la stessa lista di controlli: a sinistra tutti verdi, a destra vuoti e tratteggiati; in mezzo un file di log che non passa da una macchina all'altra.

Nel primo articolo di questa serie ho sostenuto che con gli agenti il costo dello sviluppo non sparisce: migra a monte (specificare) e a valle (verificare). Nel terzo e nel quarto il segnale più forte è arrivato dallo stesso posto: un gate che diceva “fatto” senza esserlo.

Questo quinto banco di prova ha una differenza che conta: l’input non l’ho scritto io. È un documento di feedback UX su Taita, l’app per genitori che sto costruendo, arrivato da fuori, da chi il prodotto lo ha guardato senza averlo costruito. Da quel documento sono nate due feature, portate fino in produzione con DFD e Claude Code.

Anche questo è un esperimento, non una dimostrazione. Un prodotto, due feature, uno sviluppatore. I numeri sono quelli veri, le approvazioni sono le mie: il feedback è arrivato da fuori, i gate li ho firmati io.

Il feedback, e cosa è diventato

Il documento era breve e molto concreto. In sintesi: registrazione più semplice, meno campi e meno testo all’accesso, una guida passo passo al primo ingresso, e un menu della mensa da spezzare in passaggi più semplici. C’erano anche punti più ampi, di identità del prodotto.

DFD ha fatto subito una cosa utile: ha separato ciò che era una feature da ciò che non lo era. I punti di identità sono rimasti fuori ambito, scritti come tali in entrambe le specifiche. Il resto è diventato due feature:

Feature 001: registrazione, accesso e primo ingresso Feature 003: mensa a passi
Rischio alto (tocca login e onboarding) basso, una sola dimensione media
Percorso full light
Dall’apertura al merge circa 20 ore circa 4 ore
Test red e green tracciati sui 10 task, frontend e backend; CI completa verde 42 test nuovi per 10 task
Accessibilità axe in browser reale: zero violazioni in locale, una trovata in produzione e corretta 24 schermate, due temi, zero violazioni

Il rischio non l’ho scelto a sensazione: la CLI lo calcola da cinque dimensioni (sicurezza, affidabilità, costo, impatto utenti, architettura). Toccare il login porta l’impatto a “alto”, e quello basta per il percorso full. La mensa è solo interfaccia sugli stessi endpoint, quindi percorso light. È la parte del primo articolo sulle review proporzionate al rischio che qui ho visto applicata a un prodotto vero.

Cosa ha funzionato

La specifica ha trovato un bug prima del codice. Per scrivere il passo “Da quando vale questo menu?” ho chiesto all’agente di verificare come il backend usa davvero le date, invece di copiarle dall’interfaccia esistente. Il risultato: la “settimana del ciclo” il backend la conta dalla data di inizio, mentre l’interfaccia chiedeva in che settimana “ci troviamo oggi”. Due cose che coincidono solo se inizi questa settimana. In più, senza data di fine, dopo tre giri il menu spariva ovunque senza avviso. Nessun test l’avrebbe trovato, perché i test avrebbero confermato il comportamento sbagliato. L’ha trovato l’obbligo di scrivere cosa significa ogni campo prima di disegnarlo.

Il browser reale ha visto ciò che il test non vede. Il primo audit di accessibilità in produzione della feature 001 ha trovato una violazione che in locale non esisteva: l’iframe del pulsante “Accedi con Telegram” senza titolo, perché il widget si carica solo sul dominio di produzione. È diventato un record rapido DFD (il 002, ecco perché le feature sono la 001 e la 003), cioè un fix circoscritto con un solo file di tracciamento, ed è stato corretto il giorno stesso. Nella feature 003, axe in Chromium ha trovato tre problemi di contrasto che jsdom non può vedere. Uno era il rosso dei pulsanti “Elimina”, condiviso da tutta l’app: correggerlo ha sistemato ogni pulsante distruttivo, non solo quello della mensa. È lo shift-right in piccolo: il segnale vero sta dove gira il prodotto.

Il red ha smascherato anche i test sbagliati. Nel TDD la regola DFD è che il fallimento iniziale deve dipendere dal requisito, non dall’ambiente. Questa regola ha preso tre cose:

  • un ciclo di shell scritto male faceva fallire i test perché jest non trovava il file. Era un rosso falso, e l’ho buttato;
  • un test di sicurezza falliva prima di arrivare all’asserzione sulla console. Ho aggiunto un red mirato sul vecchio codice, che ha mostrato il vero problema: il wizard scriveva in console la risposta dell’API, e Sentry la raccoglie come breadcrumb;
  • un’asserzione contraddiceva la specifica, e lì ho corretto il test.

Gli hash hanno fatto da allarme. DFD registra l’impronta SHA-256 di ogni file verificato. Nella feature 001 un file risultava “modificato rispetto all’evidenza” anche se nessuno l’aveva toccato su main. Il motivo: l’evidenza era stata registrata su una copia di lavoro con una modifica non ancora committata, cioè su uno stato che nel repository non esisteva. Qui il gate ha fatto il suo lavoro.

Cosa non ha funzionato

Le evidenze vivevano su una sola macchina. È stato il problema più grosso, e assomiglia molto a quello del quarto articolo. Il .gitignore del progetto escludeva tutti i *.log, quindi anche i log delle prove DFD. In locale i gate erano verdi, perché i file c’erano. Su qualunque altra copia del repository le stesse feature risultavano bloccate per evidenze mancanti. Per la 001 i log originali erano ancora sul disco e coincidevano con gli hash, quindi li ho committati. Per il record rapido 002 erano persi: le prove le ho rifatte davvero, con il componente com’era prima della correzione per il red e la CI rilanciata dopo il green. Le date non le ho inventate.

Nel quarto articolo il gate diceva “fatto” per uno stato non aggiornato. Qui il gate era onesto, ma onesto solo su una macchina. La correzione è una riga (!.dfd/**/*.log), ma la lezione è più ampia: un’evidenza che non viaggia con il codice non è un’evidenza, è un ricordo.

I requisiti sono arrivati a lavoro iniziato. Nella 003 io, che per ora decido il prodotto, ho aggiunto due cose dopo aver approvato il design: “il sovraccarico è anche visivo” a specifica in corso, e “si deve poter decidere fino a quando replicare il menu, sono stagionali” a implementazione quasi finita. DFD ha gestito bene la seconda: il design è passato in stale-review, quindi ho dovuto rifare l’approvazione e aggiornare il piano, e la modifica ha avuto un red suo. Ma tutto questo ha un costo, e il framework lo rende visibile, non lo elimina. È il “requisiti che emergono usando il prodotto” del primo articolo, con un prezzo esplicito: una nuova approvazione, un piano aggiornato e una parte non piccola delle quattro ore della feature.

Quel requisito ha portato alla luce un buco di dati in produzione. Per proporre “fino all’ultimo giorno di scuola” serviva il calendario scolastico regionale. In produzione, per l’anno 2026/27, c’erano solo le feste nazionali: niente inizio e fine lezioni, niente vacanze né ponti. Quindi anche le notifiche di chiusura delle scuole di quest’anno erano vuote, e nessuno se n’era accorto. Lo script di caricamento, poi, non partiva. Non era nell’ambito della feature, ed è diventato un intervento a parte. Se la feature non avesse avuto bisogno di quei dati, il buco sarebbe ancora lì.

Il TDD per task si allarga quando un componente copre tutto. Il piano aveva dieci task, ma sei vivevano nello stesso componente da riscrivere (il vecchio wizard era di 1.583 righe). Ho scritto tutti i test prima e implementato dopo: ogni red precede comunque il suo green, ma la granularità “un task, un ciclo” su un rifacimento così diventa soprattutto contabilità.

Le approvazioni sono un punto debole. Da sviluppatore unico, ogni decisione l’ha registrata l’agente con --human-confirmed, a partire dalle mie risposte in chat, a volte a domande a scelta multipla. La CLI lo dichiara apertamente: registra una decisione dichiarata, non autentica chi la prende. Funziona, ma il “responsabile umano” e lo “sviluppatore” erano la stessa persona, con un agente in mezzo a trascrivere.

Qualche attrito del framework. Un controllo testuale ha bloccato la review di rilascio perché avevo citato, in una nota, un criterio che non c’entrava con quel rilascio. La numerazione delle feature e dei record rapidi si è sovrapposta (002 era già preso). E il vincolo “la CI deve venire dopo i green”, giusto in sé, mi ha costretto a rilanciare una CI su un commit già in produzione per rimettere in ordine i tempi del record 002.

Il ruolo che manca: il PM dentro il ciclo

La cosa più interessante l’ho vista guardando da dove vengono le decisioni. Il feedback è entrato in DFD come epic della feature, cioè il documento di origine citato in specifica. Ma tutte le scelte che contavano sono state mie:

  • sovrascrittura del menu sì o no;
  • niente più ID del calendario inserito a mano;
  • un menu invernale che parte con la data da scegliere, invece che con una data proposta.

Sono scelte di prodotto, e le ha prese lo sviluppatore.

DFD ha già il posto giusto per un PM: la review di design. Prima di scrivere codice qualcuno approva obiettivo, criteri e scelte aperte; prima del rilascio qualcuno approva piano, soglie e rollback. Con un PM nel progetto il cambiamento sarebbe questo: le domande aperte della specifica non arriverebbero più a me via chat, ma a chi ha quel ruolo. E l’approvazione smette di essere una dichiarazione che mi faccio da solo.

Cosa mi porto a casa

Dal primo articolo la tesi è che il costo migra a monte e a valle. Qui l’ho visto in un caso dove i due lati si toccano: il pezzo più utile della specifica è stato leggere il codice esistente per capire cosa significano i dati. Ha trovato un bug che i test avrebbero cementato, prima di scrivere una riga nuova.

Al filo della serie si aggiunge un pezzo. Terzo articolo: una CI che non poteva fallire. Quarto: un gate fermo a uno stato vecchio. Quinto: un gate onesto, ma solo sulla macchina dove erano stati fatti i test. Un gate verde dice qualcosa solo se dice anche dove e su quale stato è verde.

Limiti da dichiarare

  • Due feature su un solo prodotto, uno sviluppatore, nessuna replica.
  • Chi ha scritto il feedback non ha approvato i gate: le decisioni registrate sono mie.
  • Il rollout della seconda feature è ancora in corso mentre scrivo: i controlli a 24 e 48 ore non sono chiusi.
  • I tempi sono quelli delle sessioni di lavoro con l’agente, non un confronto con lo stesso lavoro fatto senza DFD.
  • Parte delle date del calendario scolastico viene da due aggregatori concordi e non dagli atti ufficiali: è dichiarato nel file dei dati, ma resta una fonte di secondo livello.

Fonti nel repository (privato)

  • PR MartoEporedia/taita#697 (unita): feature 001, .dfd/features/001-ux-primo-accesso/.
  • PR MartoEporedia/taita#706 (unita): record rapido 002, titolo dell’iframe Telegram.
  • PR MartoEporedia/taita#710 (unita): !.dfd/**/*.log nel .gitignore, log delle evidenze committati, record 002 rigenerato.
  • PR MartoEporedia/taita#711 (unita): feature 003, .dfd/features/003-mensa-a-passi/.
  • PR MartoEporedia/taita#712 (unita): calendari scolastici regionali 2026/27.
#AI#agenti#spec#DFD#Claude Code#UX#accessibilità