# Registro di lavorazione del pezzo A

Questo è il diario di bordo dei ventitré task, tenuto giorno per giorno mentre
si lavorava. Dice cosa è stato fatto, ma soprattutto **cosa si è rotto e
perché**: ogni difetto trovato dalle revisioni, con la prova che lo dimostrava e
la ragione della correzione.

È qui, versionato, perché fino a oggi viveva solo su due dischi. Le decisioni
in forma leggibile stanno in `STATO.md`, che è il documento da leggere per
primo; questo è il dettaglio, per quando serve sapere perché una riga è come è.

Nasce come file di lavoro della skill che ha guidato l'esecuzione, quindi il
tono è quello degli appunti e gli accenti a volte mancano: si è preferito
lasciarlo com'era invece di riscriverlo, perché riscritto sarebbe un racconto
e non più un registro.

---

# QuantoBasta pezzo A — avanzamento

Piano: docs/superpowers/plans/2026-08-18-quantobasta-pezzo-a.md (23 task)
Ramo: pezzo-a, partito da main @ 4dc563b
Test alla partenza: 17 verdi. A piano finito: 220.

Task 1: completo (commit 8d5b456, revisione pulita). 25 test.
  Minore rimasto: i commenti di sezione in en.ts sono in inglese mentre la
  convenzione vuole l'italiano. Viene dal brief, non da chi ha implementato.
  Da valutare nella revisione finale.
Task 2: completo (commit 5b34a0b, revisione pulita). 33 test.
  Minori rimasti, tutti ereditati dal piano e non da chi ha implementato:
  - units.ts: la guardia "solo precisione ha base" in resaLeggibile non e'
    coperta da nessun test. Rimuovendola i test restano verdi. Basterebbe
    un'asserzione in lingua.test.ts: base definita implica famiglia precisione.
  - units.ts riga 19-23: il commento dice che il sort degli alias impedisce di
    leggere "fl oz" come "oz". Non e' vero: a disambiguare e' il confronto
    esatto in trovaUnita. Togliendo il sort i test restano verdi.
  - unita.test.ts: alcune uguaglianze esatte su divisioni (500/1000, 16/16).
    Valori rappresentabili in binario, quindi innocui, ma contro la convenzione.
Task 3: completo (commit 73549dd..d497616, revisione pulita). 42 test.
  Sei mutazioni provate dal revisore, tutte intercettate dai test.
  Minori rimasti: in format.ts il ramo unita === '' non ha un test che lo
  distingua da null; il letterale 100 in Math.round(q*100)/100 non e' una
  costante nominata come le altre del file.
Task 4: completo (commit 46f6115..0de07a0, revisione pulita, nessun rilievo). 45 test.
  Il revisore ha ricostruito da zero il caso che il task esiste per impedire:
  ricetta a 200 g, riscalo su 250, correzione a 180. Il fattore ricalcolato
  viene da 250/180 e non da 250/200. Verificato con uno script suo, non coi
  test del task.
Task 5: completo (commit 5ef8de7..cf14af4, revisione pulita, nessun rilievo). 49 test.
  Il test sulle icone e' stato messo alla prova aggiungendo un nome inventato al
  catalogo: diventa rosso, quindi non e' una finzione. ChiaveIcona rifiuta i
  valori fuori catalogo e i due campi nuovi di Ricetta sono davvero obbligatori,
  verificati con typecheck su file usa-e-getta.
Task 6: implementato (commit 40b2896). 54 test. Revisione: conforme, un rilievo
  Importante sulla copertura, in correzione.
  Minori rimasti, tutti ereditati dal piano:
  - numeri.ts, decimale(): la condizione "separatore && decimali" ha un ramo
    ridondante, i due si valorizzano sempre insieme per come e' fatta la regex.
  - numeri.ts, aParole(): il sort per lunghezza e' giustificato da un commento
    che dice di servire per "una noce", ma togliendolo i test restano verdi:
    a disambiguare e' gia' il controllo di confine. Stesso schema del commento
    fuorviante trovato nel Task 2 su units.ts.
  - il confine unicode \p{L} non e' provato su lettere accentate.
Task 6: completo (commit 40b2896 + correzione copertura). 54 test.
  Il rilievo Importante e' chiuso: mutando la guardia del numero misto ora un
  test diventa rosso. Verificato rompendo e ripristinando.
Task 7: completo (commit b88aafa + correzione 173d44c, ri-revisione pulita). 64 test.
  Difetto critico chiuso: una riga fatta di solo un numero ("200") produceva un
  ingrediente col nome VUOTO e marcato sicura:true. Veniva dal codice del piano,
  che a due righe di distanza enunciava la regola opposta. Ora esce come non
  capita, col testo originale come nome.
  Aperti, non bloccanti:
  - righe di sola punteggiatura o soli spazi danno ancora nome vuoto. Non e' una
    regressione ed era gia' cosi' nel test del piano: quelle righe non portano
    informazione da perdere. Se si vuole l'invariante assoluto va deciso a parte.
  - "200 di di" con doppio riempitivo da' nome "di": il ramo delle unita'
    sconosciute non controlla che la parola non sia essa stessa un riempitivo.
Task 8: completo (29f8d21 + correzioni b63bd31, c617b8e). 89 test. Tre giri di
  revisione, il punto piu' difficile del piano finora.
  Critico chiuso: una ricetta inglese incollata con l'app in italiano veniva
  riconosciuta come italiana, perche' il vocabolario italiano non conosce
  "Directions", non si fermava al procedimento e le righe finte gonfiavano il
  punteggio. Uscivano ingredienti inventati tipo "Preheat the oven to degrees"
  con quantita' 350 e marcati sicuri.
  Due correzioni di fondo: le parole che dicono "qui finiscono gli ingredienti"
  sono marcatori di struttura e vanno applicate a prescindere dal vocabolario
  che si prova; e il punteggio della lingua ora pesa il lessico riconosciuto
  davvero, non le righe che si sono lasciate spezzare.
  Limite noto e accettato: un incolla inglese in unita' metriche senza
  intestazione ("200 g flour") viene etichettato italiano, perche' g/kg/ml
  stanno in tutti e due i vocabolari e non c'e' nessun segnale che discrimini.
  Verificato che non fa danno: gli ingredienti escono corretti lo stesso e
  nessuno consuma esito.lingua a valle. Da riprendere solo se un domani
  qualcosa dipendera' da quell'etichetta.
Task 9: completo (38cdb40 + correzione 085f99f). 95 test.
  Critico chiuso: la prova sui dati veri confrontava il numero di ricette
  migrate con quello letto DALLO STESSO file, quindi togliendo tre ricette dal
  sorgente il confronto faceva 9 uguale 9 e passava. Proteggeva da tutto tranne
  che dalla perdita di dati, cioe' l'unica cosa per cui esisteva. Ora ci sono
  due costanti assolute, 12 e 77. Verificato sporcando il file vero e vedendo
  exit 1, poi ripristinato e ricontrollato da me: dati intatti.
  Tolto un ramo morto in migrate.ts che riconosceva il q.b., con il commento
  che dichiarava un rischio inesistente. Prima e' stato scritto un test che
  fissa tutte e sette le forme di q.b., visto verde col codice vecchio e
  ancora verde dopo la rimozione: la prova che il ramo era davvero morto.
Task 10: completo (a7e64f3, bc05509 + correzione 99e1ae0). 102 test.
  Interrotto a meta' dal limite di sessione fra la scrittura di db.ts e il suo
  commit: completato a mano senza toccare il contenuto.
  Critico chiuso: il database in memoria dei test (node:sqlite) nasce con le
  chiavi esterne ACCESE, mentre SQLite vero le ha spente. Quindi la riga
  "PRAGMA foreign_keys = ON" di schema.ts non era provata da niente:
  togliendola restavano tutti verdi. I test verificavano il comportamento
  della libreria di prova, non il nostro schema, e ci si sarebbero appoggiati
  altri cinque task. Ora il banco parte spento e togliendo il PRAGMA cadono
  cinque test.
  Aggiunto il test sulla cascata del riscalo, che nessuno provava.
  Differenze fra banco di prova e database vero, cercate apposta e annotate:
  - i booleani, gia' gestiti dall'adattatore
  - node:sqlite lega ogni numero come REAL, mai INTEGER. Innocuo con questo
    schema perche' ogni colonna ha affinita' dichiarata, ma da ricontrollare
    se un giorno comparisse una colonna ad affinita' debole.
Task 11: completo (commit 4b21710, revisione pulita). 115 test.
  Sei mutazioni su sette intercettate dai test. Verificato con strumentazione
  che conteggiPerCategoria fa davvero UNA query sola, anche a ricettario vuoto.
  Minori rimasti:
  - togliendo l'argomento 'it' da localeCompare nessun test cade, perche' questa
    macchina ha gia' LANG italiano. Il codice e' giusto ma la suite non
    proteggerebbe da una regressione su una macchina con locale diverso.
    Da chiudere con un'asserzione che confronti contro l'ordinamento nativo.
  - due righe di ordinamento identiche in elencoRicette e ricetteDaEsportare.
Task 12: completo (9d268c6, 6f3d3f1, revisione pulita). 124 test.
  Sei mutazioni su sei intercettate. Il revisore ha verificato eseguendo che
  ON DELETE SET NULL nello schema non e' una dichiarazione morta: scatta
  davvero su un DELETE vero, e non si contraddice con l'UPDATE del repository
  perche' agiscono su eventi disgiunti. Resta come rete per una manutenzione
  fatta a mano sul database.
  DA PORTARSI DIETRO AL TASK 15 (import): siccome la riga della categoria
  sopravvive come tombstone, niente impedisce di agganciare una ricetta a una
  categoria gia' cancellata. Quella ricetta viene contata sotto un id morto
  invece che fra quelle senza categoria, e l'elenco filtrato la restituisce
  ancora. Oggi non capita perche' gli id si pescano solo dall'elenco delle
  vive, ma un file importato da un altro dispositivo puo' benissimo riferire
  una categoria cancellata qui.
Task 13: completo (commit 7ee0d3a + copertura). 130 test.
  Il revisore ha ricostruito da zero il difetto che il task previene, in
  entrambi i versi: farina 200 -> 250 -> corretta a 180, e porzioni 4 -> 6 ->
  corrette a 8. Il fattore riparte sempre dai numeri nuovi.
  Chiuso un rilievo Importante: la validazione della FORMA della richiesta non
  era coperta, solo il JSON rotto. Disattivandola i test restavano verdi.
  Nota fuori scope: cancellaRicetta in ricette.ts cancella la riga del riscalo
  con una DELETE scritta a mano invece di chiamare dimenticaRiscalo. Funziona,
  ma e' riuso mancato.
Task 14: completo (278ac5a + correzione b28d14f). 144 test.
  Due buchi di copertura chiusi. La guardia contro i percorsi malformati in
  cancellaFoto non era dimostrabile da nessun test, perche' in Node l'errore
  veniva inghiottito comunque: estratta in una funzione pura e provata li'.
  Verificato da me con la mutazione: togliendola il test cade.
  Verificato dal revisore leggendo i file di dichiarazione veri in node_modules:
  tutte le API di Expo usate esistono con quelle firme. Il limite dei 300 KB e'
  scritto nel codice, misura il file davvero salvato e riprova scendendo di
  qualita', e all'ultimo gradino tiene comunque per scelta dichiarata.
  DA PORTARSI DIETRO AL TASK 17: il brief del Task 14 verificava app.json
  aspettandosi che i plugin fossero solo quello delle immagini. Ma un task
  precedente ci aveva gia' messo SQLite, quindi chi ha implementato ha aggiunto
  invece di sovrascrivere, giustamente. Se il brief del Task 17 ripete lo stesso
  controllo con l'atteso copiato da qui, fallira'.
Task 15: completo (c348b45, e190341, 8b3b47c + copertura 9e37f43). 167 test.
  E' il punto dove un difetto fa perdere dati, quindi il revisore ha provato a
  distruggere il ricettario con otto tipi di file rotti, confrontando ogni volta
  lo stato completo del database prima e dopo: sempre identico.
  La trappola delle categorie tombstone e' risolta bene: l'insieme delle
  categorie valide si rilegge dal database DOPO aver fuso quelle del file,
  invece di fidarsi del file. Regge su tutti e quattro i casi storti provati.
  Chiusa una lacuna dimostrata: il confronto delle date che decide quale
  versione dei dati sopravvive non era inchiodato dal caso con le due date
  IDENTICHE, l'unico dove maggiore stretto e maggiore-o-uguale differiscono.
  Ora lo e' per ricette e categorie.
  Minore aperto: un identificativo fatto di soli spazi passa la validazione,
  perche' il controllo e' sull'uguaglianza con la stringa vuota e non su
  trim(). Arriva solo da un file importato ostile e non fa perdere dati, ma
  lascia un identificativo strano nel database. Da valutare nella revisione
  finale.
  Minore: il commento su nomeFotoValido dentro leggiArchivio dice che serve
  contro i percorsi malformati, ma il revisore ha verificato costruendo uno zip
  ostile fuori da jszip che a normalizzarli e' gia' jszip. Il controllo resta
  utile per altro, la motivazione scritta e' imprecisa.
Task 16: completo (766b692 + correzioni 6ad18fb e testi). 180 test.
  Critico chiuso, ed era colpa mia: la validazione dei segnaposto che avevo
  scritto nell'addendum ispezionava la stringa GIA' sostituita, quindi una
  ricetta intitolata "Torta {della nonna}" faceva lanciare la conferma di
  cancellazione. Ora i segnaposto si ricavano dal modello e la sostituzione
  non viene piu' ispezionata.
  Corretto anche "1 ricette" al primo export, e riscritti alcuni testi inglesi
  che sapevano di traduzione automatica. Le due frasi col numero le ho poi
  riscritte io: la correzione funzionava ma aveva prodotto "riscalata —
  porzioni: 6", che non e' una frase.
Task 17: completo (2ec7e4a, ae8ccae). 183 test.
  L'agente si e' fermato dopo aver scritto i file ma prima di committarli:
  completato a mano dopo aver verificato tipi puliti e plugin aggiunti
  all'elenco invece che sovrascritti, come avvisato.
  VERIFICATO NEL SIMULATORE: l'app parte davvero. Si vedono il titolo "Quanto
  Basta" e la voce "Tutte le ricette", quindi navigazione, apertura del
  database e testi italiani funzionano.
  Nota di ambiente: expo start --ios fallisce perche' usa AppleScript per
  attivare la finestra del simulatore e il sandbox lo blocca. Si aggira
  avviando il server da solo e aprendo exp://127.0.0.1:8081 con simctl.

=== PUNTO FERMO PRIMA DEL COMPACT, 2026-08-19 ===
17 task su 23 finiti, 183 test verdi, tutto pushato su origin/pezzo-a.
Il prossimo e' il TASK 18, la schermata Categorie (la principale).
Lo stato completo, le decisioni e le trappole stanno in docs/STATO.md, che e'
versionato. Questo registro resta la fonte di verita' su quale task e' finito.
Simulatore pronto: idb installato e provato, tocca lo schermo davvero.

Task 18: completo (14d6b5b + correzioni 1aafd7e, 9993ae6). 199 test.
  Il codice consegnato era identico al brief carattere per carattere. Tutti i
  difetti li ha trovati la revisione a tre lenti, non l'esecuzione.
  Confermati e chiusi, sette distinti (dieci segnalazioni, tre doppie):
  - la tabella degli accenti aveva UNA voce collaudata su ventiquattro. Con la
    u accentata rotta, chi cercava "ragu" non trovava "Ragu' alla bolognese" e
    i test restavano verdi. Ora sono due test: uno cicla sulla tabella (becca
    una mappatura sbagliata) e uno e' ancorato a parole vere (becca una voce
    cancellata, che il primo da solo non vede perche' smette di provarla).
    Verificato mutando: prima passavano lisce, adesso diventano rosse.
  - tabella e regex erano due elenchi della stessa cosa tenuti in sincronia a
    mano. La regex e' sparita: ora ogni carattere passa dalla tabella.
  - il conteggio di categoria si leggeva col lookup nudo: un archivio importato
    con una categoria di id "toString" faceva uscire una funzione invece del
    numero, e dentro un <Text> fermava la schermata d'avvio. Ora hasOwn.
  - il tasto che svuota la ricerca si annunciava col nome del campo accanto.
    Chiave nuova ricerca.svuota, verificata nel simulatore con idb.
  - il tondo col + copriva l'ultima riga delle liste. paddingBottom 96.
  - ricerca senza risultati: schermo bianco. Chiave nuova ricerca.nulla.
  - con l'errore del database sotto restava "Tutte le ricette - 0", che sembra
    un ricettario svuotato. Ora l'errore prende tutto il corpo. Attenzione: NON
    si entra nello stato vuoto, che inviterebbe a scrivere la prima ricetta chi
    ne ha duecento. Il verificatore l'ha dimostrato mutando la produzione.
  Scartati dieci su venti, tutti con prova eseguita. Fra questi: l'apostrofo
  tipografico, il ramo "irraggiungibile" del fallback, e due accuse ai commenti.
  Rimasto aperto, minore: Icona.tsx ha una riga sola e non la collauda nessun
  test (un renderer di prova per una riga non vale il costo). Verificata a
  occhio nel simulatore: le icone delle categorie si disegnano.

ASPETTO: deciso con Paolo il 2026-08-20. "Ricettario caldo": fondo crema,
  accento terracotta #AE4F2C, titoli in Fraunces (serif), corpo col carattere
  di sistema, buio in carbone caldo. Tutto in src/ui/tema.ts, le schermate non
  scrivono piu' colori a mano. I contrasti sono verificati con la formula WCAG:
  la terracotta piu' chiara che veniva spontanea (#C4643C) si fermava a 3,8:1.
  Il ritocco fine (ombre, animazioni, icona dell'app, schermate dello store)
  resta per ultimo, quando ci sono tutte e sei le schermate.

AMBIENTE, cambiato il 2026-08-20: il progetto sul NAS sta su un mount SMB e
  Metro non ci parte (resta in stato UN, attesa di I/O, anche dopo dieci
  minuti). Adesso si lavora nella copia locale /Users/paolo/dev/quantobasta,
  si pusha su GitHub e il NAS si riallinea con git pull. Numeri: npm install
  6 minuti e mezzo sul NAS, 5 secondi e mezzo in locale.

CARATTERE, deciso da Paolo il 2026-08-20: CAVEAT per il nome dell'app, piu' il
  sottotitolo tradotto "Dosatore di ricette" / "Recipe scaler" sotto al nome,
  nell'intestazione. Commit 700d6b0.
  Due campionari HTML pubblicati come artifact per far scegliere:
  - primo giro, dieci caratteri di tutti i registri:
    https://claude.ai/code/artifact/a953e34b-6e9f-4f90-a1d7-1600436f65dd
  - secondo giro, undici mani scritte attorno a Caveat:
    https://claude.ai/code/artifact/94b525f7-206b-426e-93c4-637e7bfd05c8
  Due cose imparate, valide per qualunque corsivo si usi in futuro:
  1. Caveat ha l'occhio piccolo: alla misura di sistema dell'intestazione (22)
     risulta minuta, va portata a 27. Nel campionario le due misure erano
     affiancate apposta, ed e' cosi' che si e' visto.
  2. L'interlinea va dichiarata a mano (34 su corpo 27): nei corsivi le code
     sotto la riga di base — la Q di "Quanto" — con l'interlinea automatica
     vengono tagliate dal bordo del testo.
  La mano scritta vale SOLO per il marchio: i titoli delle altre schermate sono
  contenuto dell'utente e restano col carattere del dispositivo.
  Verificato nel simulatore: intestazione a due righe, niente taglio, sottotitolo
  leggibile. Fraunces disinstallato, nessun residuo in src/.

Task 19: completo (330b446 + correzioni 15f845d, 0c4e345, 1c23239). 210 test.
  Revisione a tre lenti, ognuna in un worktree separato: 10 segnalazioni, 5
  difetti distinti, tutti confermati eseguendo il codice.
  - il piu' grave: aprendo un elenco, per tutta la durata del caricamento si
    leggeva "Nessuna ricetta con questo nome" col campo di ricerca vuoto, sopra
    un ricettario pieno. Invisibile agli screenshot (il caricamento e' troppo
    veloce): trovato compilando il .tsx e pilotandone gli stati a mano.
  - il tondo col + si annunciava a VoiceOver con la frase d'invito dello stato
    vuoto invece che "Nuova ricetta".
  - il commento di statoElenco e il suo test dichiaravano il contrario di quello
    che la schermata fa (dicevano "non si scrive nulla").
  - il confine `visibili > 0` non collaudato: mutato in `> 1` i test restavano
    verdi, e una categoria con UNA ricetta avrebbe mostrato "scrivi la prima".
  - l'intestazione di una categoria restava vuota per tutto il caricamento, e
    per sempre se la categoria era stata cancellata altrove.
  Il ripiego per la categoria cancellata l'ho cambiato io: l'agente aveva
  riusato 'Nessuna categoria', che nell'app vuol dire un'altra cosa ed e' il
  nome di un filtro. Chiave nuova elenco.titolo.sparita.

DECISIONE DI STRUTTURA, 2026-08-20 (commit 1c23239). La ri-revisione ha
  dimostrato che nessun test copre i .tsx: OTTO mutazioni su otto dentro
  Elenco.tsx lasciavano i 204 test verdi, comprese due che riportavano indietro
  difetti appena corretti. Da qui in poi e' tutta interfaccia, quindi il buco
  peggiorava.
  Scelta: NON si installa un motore di test per componenti (jest,
  testing-library). Si portano le decisioni dentro i moduli puri
  src/ui/logica-*.ts, che node --test carica gia'. Spostate: la sentinella
  `caricato` dentro statoElenco (che ora ha lo stato 'caricando'), e il titolo
  dell'intestazione in titoloElenco (cinque rami, prima nel JSX).
  Chiusa nell'occasione la stessa finestra su Categorie.tsx, dove il
  ListEmptyComponent dipendeva solo da `cercando`: scrivendo nella ricerca
  durante la prima lettura compariva "non ho trovato niente" a sproposito.
  Verificato da me mutando: sentinella tolta -> 1 rosso; confine a 1 -> 1 rosso;
  ripiego del titolo invertito -> 2 rossi. Prima erano tutte verdi.
  REGOLA PER I TASK 20-23: se una schermata prende una decisione, quella
  decisione sta in un modulo puro col suo test. Nel .tsx resta solo il disegno.

NOTA DI PROCESSO: nel workflow di revisione avevo dato una cartella per LENTE,
  ma i verificatori di ogni lente ereditavano la stessa cartella e si sono
  pestati i piedi (un revisore ha visto un file mutato sotto le mani e si e'
  estratto una copia sua con git archive). La prossima volta: una cartella per
  AGENTE. E l'isolamento automatico del Workflow (isolation: 'worktree') su
  questa macchina NON funziona: l'hook WorktreeCreate non restituisce il
  percorso e i tre agenti muoiono all'avvio. I worktree vanno creati a mano con
  git worktree add e passati nel prompt.

Task 20: completo (27750a7 + correzioni 3a51356, fae1542). 230 test.
  E' il cuore dell'app e la revisione e' stata la piu' larga: quattro lenti
  (numeri, schermata, contratti, e una che controllava se la regola sulle
  decisioni fuori dal .tsx fosse stata applicata sul serio), ogni agente in una
  copia sua fatta con git archive nello scratchpad. 27 agenti, 8 difetti
  confermati su 23 segnalati, 5 distinti piu' uno trovato da me nel simulatore.
  TRE CRITICI, tutti sul numero che l'utente scrive:
  - quantitaPerRichiesta divideva per la quantita' ARROTONDATA a schermo,
    quindi l'errore di arrotondamento della riga si moltiplicava su tutti gli
    altri ingredienti. Sulle unita' discrete la conversione dovrebbe essere 1 e
    restava solo l'errore: chiedendo "3 uova" l'app dosava per 3,2 uova, farina
    360 g invece di 300, e la richiesta sbagliata finiva SALVATA nel database.
    Il commento che lo giustificava era falso, e la falsita' veniva dal piano.
    Corretto usando resaLeggibile() per la conversione, che da' la quantita'
    convertita NON arrotondata. VERIFICATO NEL SIMULATORE sulla Ciambella vera:
    3 uova su 4 danno farina 375 g esatti, zucchero 225, burro 131, porzioni 23.
  - il campo teneva il numero mentre l'unita' accanto cambiava: aperto su
    "580 g", toccando le porzioni finche' la riga diventava "1,16 kg", si
    confermava e la ricetta veniva riscalata MILLE volte. Due tocchi.
  - il campo non selezionava il contenuto: toccando "50 g" e scrivendo 75 usciva
    5075. Questo l'ho trovato io toccando lo schermo, non i revisori.
  Piu': "Porzioni 0" con i due tasti che facevano la stessa cosa (il "-"
  aumentava); il "-" a porzioni 1 buttava via il riscalo per ingrediente
  cambiando le dosi senza cambiare il numero; e la fascia diceva "riscalata per
  6" senza la parola "porzioni", poi "1 porzioni" al singolare.
  Le due chiavi per il singolare le ho autorizzate io: l'agente si era fermato
  perche' gliele avevo vietate, ed era giusto che si fermasse.

COLLAUDO: da oggi il simulatore ha le DODICI RICETTE VERE di Paolo con i loro
  77 ingredienti. Le ricette finte che c'erano non avevano ingredienti, quindi
  il Dosatore non si poteva collaudare affatto. Lo script sta nello scratchpad
  (semina.ts), non nel repository: legge il ricettario della vecchia webapp,
  lo passa per migraArchivio() e scrive nel file SQLite del simulatore riusando
  salvaRicetta/salvaCategoria e lo stesso adattatore node:sqlite dei test, cosi'
  se lo schema cambia lo script segue. L'app va CHIUSA mentre gira.
  Il file: ~/Library/Developer/CoreSimulator/Devices/<UDID>/data/Containers/
  Data/Application/<GUID>/Documents/ExponentExperienceData/@anonymous/
  quantobasta-*/SQLite/quantobasta.db

Task 21: completo (ae54b7f + correzioni 72594c9). 242 test.
  Revisione a tre lenti: 26 segnalazioni, ma il LIMITE DI SESSIONE ha ucciso 17
  verificatori su 29. Solo 3 confermati formalmente; gli altri 23 NON erano ne'
  confermati ne' respinti, e trattarli da scartati sarebbe stato l'errore
  opposto. Li ho passati al correttore con l'obbligo di verificarli eseguendo
  prima di toccare. Esito: 13 veri su 14, uno respinto con buona ragione.
  Confermati e chiusi:
  - il tasto "Salva" da spento era BIANCO SU CREMA, 1.30:1, cioe' invisibile: ed
    e' lo stato in cui il foglio si apre ogni volta, perche' il nome parte
    vuoto. Si vedeva una pillola pallida e muta accanto ad "Annulla" leggibile.
  - le etichette delle trenta icone erano le chiavi interne del catalogo, mai
    tradotte: in inglese VoiceOver leggeva trenta parole italiane.
  - le frecce spente restavano pulsanti attivi per il lettore di schermo.
  Trovati dalle lenti e poi verificati dal correttore:
  - IL PRIMO TOCCO SU UN'ICONA NON ARRIVAVA (segnalato da tre lenti su tre): il
    campo del nome ha autoFocus, quindi la tastiera e' aperta per costruzione, e
    senza keyboardShouldPersistTaps la ScrollView prende il tocco in fase di
    cattura. Provato leggendo il sorgente installato di react-native 0.86.2,
    ScrollView.js:1487-1518, dove il commento originale descrive il difetto.
  - il foglio non si spostava per la tastiera: "Salva" a y 814-858 con la
    tastiera che parte da 538, quindi la categoria non si poteva salvare.
  - due tocchi su "Salva" creavano due categorie identiche, e due tocchi su una
    freccia davano "cannot start a transaction within a transaction" perdendo lo
    spostamento: withTransactionAsync di expo-sqlite NON e' esclusiva.
  - il messaggio d'errore mentiva: dopo una SCRITTURA fallita diceva "non riesco
    ad aprire il ricettario, le ricette non sono state toccate". Chiave nuova
    errore.scrittura; errore.db resta solo dove la lettura fallisce davvero.
  - i tre comandi in fondo alla riga erano sotto i 44pt e le aree di tocco si
    sovrapponevano per 4 punti, dove vinceva la ×, cioe' quella che cancella.
  - l'ultima riga di icone era visibile per 12 punti su 44.
  Respinto: "il commento dice che i colori non si scrivono qui in un file che li
  scrive". Il correttore ha verificato che la stessa riga sta IDENTICA in
  Elenco.tsx e Categorie.tsx, gia' revisionate, e che il colore in questione e'
  il nero di un'ombra, non un colore della tavolozza. Correggerla solo qui
  avrebbe aperto una divergenza invece di chiuderne una.
  Aggiunti da me: il token `attenzione` nel tema (#8A6A1F chiaro, #D9A63C
  scuro, contrasti verificati col calcolo WCAG), perche' l'avviso di nome
  duplicato usava il rosso dell'errore bloccante mentre li' il gesto e'
  permesso; e le icone vere al posto dei glifi di testo per frecce e ×.
  VERIFICATO NEL SIMULATORE, tutti i punti che il correttore aveva potuto
  provare solo sulla carta: Salva leggibile da spento, trenta icone tutte
  visibili, foglio che si alza con la tastiera (Salva passa da y=814 a y=479),
  e il tocco su un'icona che arriva AL PRIMO COLPO con la tastiera aperta.

NOTA DI AMBIENTE: i revisori avevano creato e avviato simulatori loro
  (QB-rev-dati0, QB-rev-dati1), e da quel momento `xcrun simctl ... booted` non
  puntava piu' all'iPhone 17 ma a uno dei loro. Da qui in poi USARE SEMPRE
  L'UDID ESPLICITO, mai "booted". Spenti e cancellati.
  Seconda trappola: `simctl openurl` fa comparire un alert di sistema ("Vuoi
  aprire l'elemento in Expo Go?") che idb NON riesce a toccare, perche' e' di
  SpringBoard e non dell'app, e blocca ogni automazione. Si esce riavviando il
  simulatore (shutdown + boot) e riaprendo l'url.

Task 22: completo (d651f9a + correzioni c47d5eb). 275 test.
  Il task piu' grosso del piano (1845 righe di brief) e l'unica schermata che
  SCRIVE. Revisione a quattro lenti, una dedicata al solo testo incollato.
  14 conferme su ~7 difetti distinti, TRE CRITICI che perdevano il lavoro:
  - "SALVA" NON LEGGEVA IL RIQUADRO DELL'INCOLLA. Il giro naturale (scrivo il
    titolo, incollo gli ingredienti, tocco Salva) salvava la ricetta SENZA
    ingredienti e buttava via il testo, senza avvisi: la lettura stava in
    onBlur. Tre lenti su quattro. VERIFICATO DA ME NEL SIMULATORE dopo la
    correzione, guardando il database: la ricetta "Prova" adesso arriva a
    disco con farina 300 g.
  - la foto riscriveva la bozza con una copia catturata prima: setBozza({...
    bozza, foto}) non funzionale. Mentre l'immagine si comprime l'app lascia
    scrivere, e alla fine tutto quello che si era scritto tornava indietro.
  - rifare la foto di una ricetta che ne aveva gia' una cancellava la vecchia
    dal disco SUBITO, prima di qualunque Salva: "Annulla" non la riportava e il
    database puntava a un file sparito. Il commento diceva il contrario.
  Maggiori: la fila delle categorie senza keyboardShouldPersistTaps (stesso
  difetto del Task 21, un revisore l'ha dimostrato caricando in Node la vera
  ScrollView di react-native e chiamandone il gestore di cattura); la × che
  toglie UN ingrediente si annunciava "Elimina ricetta <nome>"; Salva restava
  acceso durante la compressione della foto; sostituendo la foto non cambiava
  niente a schermo.
  Due li ho trovati io nel simulatore: i pulsanti delle categorie si
  annunciavano col glifo grezzo dell'icona ("󰩰, Categoria numero 1"), e
  bozzaDaTesto poteva inventare una quantita' — con "Sale a.b." vinceva il
  parser inglese, che legge "a" come articolo, e la riga usciva q="1" e
  incerta=FALSE mentre leggiBlocco da solo diceva q=null, sicura=false.
  Tre chiavi nuove autorizzate: modifica.riga.togli, modifica.riga.aggiungi,
  modifica.incolla.niente.
  DECISIONE: "Salva" NON si blocca quando il riquadro contiene testo che il
  parser non capisce. Chi ha incollato il procedimento per sbaglio non
  riuscirebbe piu' a salvare, ed e' un danno peggiore di un testo di scarto che
  se ne va. Il correttore l'ha proposto e me l'ha detto invece di nasconderlo.

Task 23: completo (b42159a + correzioni 1047139). 299 test.
  L'ultimo del piano, e ha prodotto i difetti peggiori: l'IMPORT NON ERA ATOMICO
  e quando si rompeva raccontava il contrario di quello che era successo.
  - un file di 40 ricette con la ventesima malformata ne lasciava dentro 19 e
    scriveva "File non importato: non e' un ricettario di QuantoBasta". E siccome
    onImportato() non veniva chiamato, l'elenco non si rileggeva nemmeno.
  - withTransactionAsync di expo-sqlite NON e' esclusiva, e il suo JSDoc lo
    dichiara: una qualunque scrittura da un'altra schermata uccideva l'import a
    meta'. Riprodotto iniettando un salvaRicetta in mezzo a un import di 20: ne
    restavano 4, 9, 14 o 19 a seconda di quando cadeva il tocco.
  - id duplicato dentro lo stesso file: vinceva l'ULTIMO invece del piu' recente,
    perche' le mappe dei locali erano fotografate prima del ciclo e non si
    aggiornavano; e il conteggio mostrato mentiva.
  - navigation.replace dopo l'import distruggeva la schermata che l'utente stava
    leggendo, e il commento lo giustificava con un problema inesistente.
  RIMEDIO: import tutto-o-niente con withExclusiveTransactionAsync.
  DUE COSE TROVATE DAL CORRETTORE, nessuna delle due era nel mandato:
  1. la transazione esclusiva apre una CONNESSIONE NUOVA, e PRAGMA foreign_keys
     vale per connessione: quella non passa da applicaMigrazioni e dentro una
     transazione il PRAGMA non si puo' riaccendere. scriviRicetta si fidava di
     ON DELETE CASCADE, quindi ogni AGGIORNAMENTO di ricetta gia' presente
     sarebbe morto su UNIQUE constraint failed: ingredienti.id — cioe' il caso
     d'uso principale dell'import, rotto da un rimedio che avevo chiesto io. Se
     n'e' accorto mentre lo scriveva. Ora gli ingredienti si cancellano a mano,
     con un test che gira a chiavi esterne spente.
  2. scriviFoto sta fuori dalla transazione (giustamente) ma se lanciava, l'
     eccezione risaliva e diventava "File non importato" con le ricette dentro.
  E LA RADICE VERA del primo difetto non era l'atomicita': validaFile prometteva
  "null se non e' una ricetta leggibile" e poi sui gruppi faceva un cast cieco
  (r.gruppi as ...). La ventesima ricetta non era una che l'import non sapeva
  gestire: era una che il validatore aveva dichiarato buona senza guardarla.
  Chiuse tutte e due: la causa e la protezione da quelle che non conosciamo.
  Chiavi nuove: io.condividi.errore, io.import.scartate.una/.molte, piu' il
  motivo 'scrittura-fallita' in scambio.ts.
  Un rifiuto ben motivato: il controllo di unicita' degli id dentro validaFile
  avrebbe coperto meta' del problema (un id di gruppo puo' collidere anche con
  uno gia' nel database, che formato.ts non vede) dando l'impressione di
  coprirlo tutto. Cioe' lo stesso difetto che stavamo chiudendo.
  VERIFICATO NEL SIMULATORE: l'export produce un archivio valido, aperto e
  controllato da fuori — 180 ricette, 22 categorie, la Besciamella coi suoi 5
  ingredienti. L'import dall'interfaccia NON e' stato collaudato a schermo: il
  selettore di file del simulatore non si automatizza, e la barra in alto a
  destra e' coperta dal pulsante flottante di Expo Go (per il collaudo l'ho
  spostata temporaneamente a sinistra, poi rimessa).

=== TUTTI E 23 I TASK SONO FINITI. 299 test verdi. ===
Resta la revisione finale dell'intero ramo, poi la chiusura.

=== REVISIONE FINALE DEL RAMO (5c3d403). 299 -> 311 test. ===
Cinque lenti sull'INSIEME, non sui singoli task (quelli erano gia' passati):
coerenza fra le schermate, difetti ricorrenti, giro completo dei dati, buchi di
collaudo, pubblicazione. Il limite di sessione ha ucciso la prima esecuzione,
rilanciata in due ondate.
LA SCOPERTA CHE CONTA: quasi tutti i difetti trovati erano gia' stati corretti
in una schermata e rimasti aperti nella GEMELLA. Sono i piu' insidiosi, perche'
il codice corretto accanto da' l'impressione che siano chiusi ovunque.
Otto punti, tutti chiusi:
- il DOSATORE era l'unica schermata senza gestione degli errori: in lettura
  restava bianca per sempre, in scrittura mostrava un riscalo non salvato.
- SceltaCategoria non aveva la guardia sulla scrittura in volo (chiusa nel Task
  21 nel gemello GestioneCategorie): due tocchi = due categorie identiche, la
  ricetta si attacca alla seconda e la prima resta orfana per sempre.
- LA PROTEZIONE DEL TASK 8 NON ERA COLLAUDATA DA NIENTE: sostituendo il corpo
  di troncaAlProcedimento con `return testo;` i 299 test restavano verdi, e il
  difetto critico (ricetta inglese incollata dal web che produce "Heat the oven
  to C" fra gli ingredienti) sarebbe tornato. Il motivo per cui nessuno se n'era
  accorto: i blocchi di prova avevano OTTO ingredienti contro DUE righe di
  procedimento, e con quella proporzione la lingua giusta vince comunque. I
  blocchi nuovi hanno la proporzione delle ricette vere: senza troncamento vince
  la lingua sbagliata 7 a 4, in tutt'e due i versi.
- un archivio che porta la CANCELLAZIONE di una categoria lasciava le ricette
  locali appese a un id morto: sparivano da tutte le viste tranne "Tutte le
  ricette". Estratta `sganciaRicette`, usata sia dall'import sia da
  cancellaCategoria, invece di riscrivere la stessa regola in SQL diverso.
- il Dosatore era l'unica vista scorrevole con un campo che non si scansa dalla
  tastiera; i due tasti delle porzioni erano gli unici comandi dell'app senza
  etichetta (chiavi nuove dosatore.porzioni.meno/.piu); e il "-" a una porzione
  non era mai disabled.
- CampoQuantita era l'unico bersaglio sotto i 44 punti (ne misurava 37), ED E'
  IL GESTO CENTRALE DEL PRODOTTO. DECISIONE DI PRODOTTO: portato a norma anche
  se la riga cresce da 45 a 52 punti (70 punti di scorrimento in piu' su dieci
  ingredienti). Si tocca con le dita sporche di farina: la misura vince.
- validaFile accettava quantita' negative e zero da un file importato.
DECISIONE, dubbio 3 del correttore: nel Dosatore l'errore NON copre una ricetta
gia' a schermo se fallisce la sola rilettura. Le gemelle sostituiscono sempre,
ma loro mostrano elenchi; il Dosatore mostra i numeri che uno sta usando davanti
ai fornelli, e portarglieli via per annunciare un guasto che in quel momento non
gli serve sapere sarebbe irrispettoso.
VERIFICATO NEL SIMULATORE: i due tasti si annunciano col nome giusto e misurano
44 punti, il riscalo salvato si riprende all'apertura.

=== SECONDA ONDATA DELLA REVISIONE FINALE (f6efa48). 311 -> 332 test. ===
Un CRITICO nel cuore del calcolo, trovato solo perche' una lente ha provato
l'app come la userebbe una persona:
  LE PORZIONI FRAZIONARIE MENTIVANO. scala() mostrava
  Math.max(1, Math.round(porzioni * f)), ma quello stesso numero arrotondato
  tornava dentro richiestaPorzioni() come base per il tasto "+". Ricetta per 2
  con 4 uova: chiedi 1 uovo, lo schermo dice "Porzioni 1" (il fattore vero e'
  0,25), tocchi "+" aspettando una porzione in piu' e vai a 2, cioe' alle dosi
  originali: QUATTRO VOLTE TANTO, senza avvisi.
  RIMEDIO scelto dal correttore, e la sua motivazione e' migliore della mia
  richiesta: mostrare il numero VERO anche frazionario invece di nascondere il
  contatore. "Nascondere rende vero quel che resta togliendo un'informazione,
  mostrare la frazione lo rende vero aggiungendone." Piu' l'argomento che chiude
  la questione: l'intestazione di scaling.ts, scritta il primo giorno, elenca
  "quante porzioni vengono fuori" fra le domande a cui l'app deve rispondere.
  VERIFICATO NEL SIMULATORE sulla Minestra vera (2 porzioni, 600 g di zucca):
  chiedendo 150 g si legge "Porzioni 0,5", e il "+" porta a "Porzioni 1,5" con
  450 g. Una porzione in piu' di quella scritta, come promette l'etichetta.
Piu': cinque guardie del motore di riscalo (zero, NaN, Infinity) che nessun test
  copriva; l'ordine degli ingredienti dentro una ricetta, non provato ne' in
  scrittura ne' in lettura (4 mutazioni su 4 sopravvivevano, compresi i due
  ORDER BY); il `continue` dell'import che, tolto, faceva svuotare una categoria
  viva da un archivio piu' vecchio; il ramo 'ingrediente' di leggiRichiesta; il
  numero annunciato prima di condividere; la tolleranza delle frazioni,
  collaudata solo verso l'alto.
  La guardia "una scrittura per volta" era riscritta a mano in due punti su
  quattro: unificata su scritturaUnica.ts. E tre decisioni sono uscite da
  Categorie.tsx (statoCategorie, descriviConteggio), che era rimasta indietro
  rispetto alla gemella Elenco.tsx.
PRONTI PER GLI STORE (in app.json, tutte a costo zero adesso e care dopo):
  - i due testi dei permessi esistevano SOLO IN ITALIANO su un'app bilingue:
    aggiunta la chiave `locales` con assets/locales/it.json e en.json,
    verificata con `expo prebuild` che genera le due .lproj;
  - supportsTablet era `true` ereditato dallo scaffold, mai deciso: obbligava a
    fornire gli screenshot iPad e a farsi provare l'app su iPad. Ora false.
  - manca(va) ios.config.usesNonExemptEncryption: ogni caricamento si fermava
    sulla domanda dell'export compliance. Ora false.
  - lo sfondo dell'icona Android era l'azzurro del template: ora il crema #FDF8F3.
  - IL FONT CAVEAT ENTRAVA NEL BUNDLE QUATTRO VOLTE: l'import dalla radice di
    @expo-google-fonts/caveat fa quattro require a livello di modulo. Misurato
    con expo export: 1030 KB e 21 asset prima, 257 KB e 18 dopo.
RESTANO DA FARE, e sono lavoro di disegno mio, non di codice:
  - l'ICONA DELL'APP e' ancora quella predefinita di Expo (la freccia azzurra
    con le linee guida): va rifatta con la tavolozza e il carattere scelti;
  - la SCHERMATA D'AVVIO non e' configurata (expo-splash-screen non installato).
NOTA DI PROCESSO, errore mio: ho aggiunto una chiave i18n mentre l'agente stava
  lavorando sugli stessi file, credendolo fermo perche' non mi era arrivato il
  suo messaggio. Ne sono uscite due voci identiche e tsc si e' fermato. Stessa
  lezione dei revisori del Task 18: quando qualcun altro ha le mani nel file, si
  aspetta. Ripulito tenendo la sua versione.

=== RAMO CHIUSO, 2026-08-21 ===
pezzo-a fuso in main con f1fcadc (merge non fast-forward, 66 commit) e
cancellato in locale e sul remoto. Test verificati DOPO la fusione: 332 verdi,
tsc pulito. Da adesso si lavora su main.

=== COLLAUDO MANUALE COMPLETO, 2026-08-21 (6cc692f). 332 -> 340 test. ===
Paolo ha chiesto se avevo provato ogni flusso come farebbe una persona. Non
l'avevo fatto: mancavano la foto, la modifica di una ricetta esistente,
l'eliminazione, la creazione e gestione delle categorie fino in fondo, e il
primo avvio a ricettario vuoto. Fatto tutto, partendo da un database svuotato.
PASSATE: primo avvio vuoto con l'invito; creazione della prima ricetta; creazione
  di una categoria al volo con la sua icona, gia' selezionata al ritorno;
  incolla + Salva diretto; apertura nel dosatore; modifica di una ricetta
  esistente (carica tutto, cambia una quantita', toglie una riga); LA FOTO per
  intero (permesso, scelta dalla libreria, compressione da 800 KB a 31 KB, NOME
  del file nel database e file su disco); eliminazione con conferma che dice il
  titolo, tombstone e foto cancellata dal disco; riordino delle categorie
  (verificato anche sul disco, ordine 0..n); rinomina con avviso di duplicato nel
  token `attenzione` e non in `errore`; e soprattutto CANCELLAZIONE DI UNA
  CATEGORIA CON DENTRO TRE RICETTE: le tre ricette restano e passano a "senza
  categoria", esattamente quello che la conferma promette.
IL DIFETTO TROVATO, e la diagnosi non era nessuna delle due che avevo in mente:
  toccare "+" sulle porzioni quattro volte di fila non faceva NIENTE, e sul disco
  non restava nessun riscalo. Un tocco solo invece funzionava e persisteva.
  NON erano le scritture concorrenti (salvaRiscalo non apre transazioni) e NON
  era solo la closure vecchia. ERA LA FASCIA CHE SPOSTAVA IL TASTO DA SOTTO IL
  DITO: FasciaRiscalo compare al primo tocco, stava SOPRA il controllo delle
  porzioni, e spingendo giu' tutto di ~55 punti faceva finire il secondo tocco su
  "Dosi originali", che sta nello stesso angolo destro quasi alla stessa altezza.
  E "Dosi originali" il riscalo lo CANCELLA: ecco perche' il disco era vuoto e
  non compariva nessun errore. I tocchi si annullavano a coppie.
  VERIFICATO DA ME sul codice vecchio, con la prova che l'agente stesso ha
  proposto per farsi smentire: dopo un tocco, "Dosi originali" occupa y 183-201
  e il dito puntava y=192. Tre tocchi lasciavano 31 sulla Ciambella (30+1, poi
  annulla, poi +1) con la riga sul disco, esattamente come previsto.
  RIMEDIO: la fascia scende sotto il controllo delle porzioni, che ora sta a
  un'altezza fissa; i tasti partono dalle porzioni mostrate ADESSO (useRef, non
  l'ultimo disegno); le scritture si accodano con creaScrittoreUltimo(), perche'
  expo-sqlite manda le runAsync su una coda dichiarata concorrente e l'ordine
  non e' garantito. VERIFICATO NEL SIMULATORE: quattro tocchi rapidi portano la
  Ciambella da 30 a 34, fascia e disco d'accordo.
MINORE CHIUSO: la barra di ricerca compariva a ricettario vuoto in TUTTE E DUE
  le schermate che cercano, non in una sola: sparisce dove il corpo e' l'invito
  a scrivere la prima ricetta.
LEZIONE: nessuno dei sei giri di revisione multi-agente aveva trovato questo
  difetto, perche' non si vede leggendo il codice ne' eseguendolo: si vede solo
  toccando due volte lo stesso punto dello schermo.

=== TRASCINAMENTO DELLE CATEGORIE, 2026-08-21 (a46a2c2, ca1363a). 340 -> 349 test. ===
Paolo ha chiesto se invece delle frecce si potesse trascinare. Fatto SENZA
nessuna dipendenza nuova: PanResponder e Animated stanno gia' in React Native, e
le categorie sono poche e tutte alte uguale, quindi sapere dove finisce il dito
e' una divisione. Scartate: la lista nativa di @expo/ui (solo iOS, e imporrebbe
l'aspetto di SwiftUI al posto del nostro) e react-native-draggable-flatlist
(tira dentro reanimated e gesture-handler, due dipendenze native pesanti).
LA COSA DA SAPERE, che sembra sbagliata ed e' quella che lo fa funzionare: il
  PanResponder non sta sulle righe, sta SOPRA la lista. Su iOS la lista e' una
  UIScrollView vera e al primo movimento si prende il dito annullando il tocco a
  chi sta dentro; React Native disabilita lo scorrimento solo se chi ha preso il
  gesto e' un ANTENATO (RCTScrollView, _shouldDisableScrollInteraction risale la
  gerarchia). Quale riga si sta toccando lo dice la riga stessa in cattura.
TOCCO CONTRO TRASCINAMENTO: al primo contatto non si prende niente, quindi la
  rinomina parte come sempre; si diventa trascinamento a 8 punti di movimento e
  solo se e' piu' verticale che orizzontale (la seconda condizione salva lo
  scorrimento all'indietro dal bordo). Nessun timer.
ACCESSIBILITA': le frecce spariscono dalla vista ma restano come azioni
  personalizzate (accessibilityActions + onAccessibilityAction), con le chiavi
  i18n che c'erano gia'. Sulla prima riga solo "Sposta in basso", sull'ultima
  solo "Sposta in alto": un'azione che non fa niente e' peggio di una assente.
  VERIFICATO NEL SIMULATORE leggendo custom_actions riga per riga.
COLLAUDATO DA ME, i quattro rischi che l'agente stesso aveva elencato:
  1. tocco secco -> si apre la rinomina (sì); 2. trascinamento di «Primi» dalla
  quarta posizione alla prima -> ordine cambiato a schermo E sul disco, rinomina
  mai aperta; 3. istantanea a meta' trascinamento: la riga e' sollevata, bianca,
  con l'ombra, e le vicine si sono scostate; 4. le azioni di VoiceOver ci sono e
  sono asimmetriche ai bordi, come devono.
COMPROMESSO DICHIARATO: Animated.event su PanResponder non puo' usare il driver
  nativo (gli eventi nascono in JavaScript), ed e' esattamente il motivo per cui
  esistono reanimated e gesture-handler. Con una decina di categorie non si
  vede: durante tutto il trascinamento React ridisegna DUE volte. Con un elenco
  molto lungo e il thread JS occupato la riga potrebbe restare indietro.
LIMITI NOTI: niente scorrimento automatico ai bordi (si sposta una riga fin dove
  arriva il dito sullo schermo), e la rampa con cui i vicini si scostano e'
  lineare e non a molla.
UNA NOTA DI METODO dall'agente, che vale oltre questo task: alla prima stesura
  il ciclo era `i !== destinazione` e una mutazione non falliva, girava
  all'infinito e npm test restava APPESO. Un rosso e' un rosso, ma un test
  appeso non dice niente a chi verra' dopo: il ciclo adesso conta prima quanti
  scambi fare, e la stessa mutazione fallisce in 2 ms.
