A settembre 2024 ho passato tre settimane a costruire un editor a blocchi. L'idea era la solita: ogni paragrafo è un blocco, lo trascini, lo riordini, e ogni blocco ha il suo pulsante di IA per riscriverlo. Niente di esotico. Notion lo fa da anni.
L'ho montato con dnd-kit per il trascinamento e un'istanza di editor per blocco. Il componente Block riceveva id, index, content, onDelete, onContentChange, una funzione regenerateContent con cinque parametri, l'intera lista di blocchi per il contesto, e un AbortController in un ref per interrompere la generazione a metà. Funzionava. Uno dei commit di quella settimana si chiama letteralmente "never give up!!". È quello che è rimasto scritto.
Sei settimane dopo tutto questo era cancellato e al suo posto c'era un solo editor con estensioni.
Cosa si rompe quando sei tu a orchestrare i blocchi
Il problema non era il trascinamento. Il trascinamento è venuto bene. Il problema era tutto il resto, e l'ho scoperto in quest'ordine:
- Sincronizzare il contenuto verso l'interno. Ogni blocco aveva un
useEffectche confrontavacontent !== editor.getHTML()per chiamaresetContent. Confrontare HTML come stringa per decidere se lo stato è cambiato è esattamente il tipo di cosa che sembra funzionare finché non funziona più. - Selezione tra blocchi. Non esiste. Se ogni blocco è il suo editor, l'utente non può selezionare da metà di un paragrafo fino a metà del successivo. Non c'è una correzione parziale: o c'è un documento o ci sono N documenti.
- Annullare. Ogni istanza ha il suo stack di cronologia. Ctrl+Z annulla nel blocco dove sta il cursore, non l'ultima cosa che hai fatto. Nessuno se lo aspetta.
- Incollare. Incolli tre paragrafi in un blocco e restano tutti e tre dentro un blocco. Spezzarli significa reimplementare a mano quello che lo schema del documento già sa fare.
Nessuna di queste quattro cose era nella mia testa quando ho iniziato. Tutte e quattro sono comparse nello stesso ordine in cui le ho scritte.
La riscrittura è stata più corta del prototipo
A metà ottobre la cartella block/ conviveva già con una cartella block2/ che aveva tre file: l'editor, la barra degli strumenti e un hook. L'hook completo erano quaranta righe: StarterKit, TextStyle, FontFamily, TextAlign, Highlight, Placeholder. Sei estensioni dichiarate in un array, e con quelle arrivavano la cronologia unificata, la selezione continua e l'incollaggio con schema. A novembre sono arrivate le liste di attività, i blocchi di codice e le immagini da URL: un import e una voce nell'array ciascuna.
Il nome della cartella dice tutto. block2 non è un refactor. È ammettere che quello lì accanto non si salva.
Lo stesso con il CSS di questo sito. Avevo un file di milleseicentoottantuno righe con classi mie. Passandolo a componenti, l'unica cosa sopravvissuta sono stati i token di colore: centotredici righe in globals.css. Il resto era reimplementazione.
Dove la libreria vince sempre
Non è "usa librerie". È che ci sono categorie concrete dove il problema è più grande di quanto sembri da fuori, e sono sempre le stesse:
- Editor di testo formattato. Selezione, cronologia, incollaggio e schema sono un sistema solo. Non puoi prenderne tre su quattro.
- Autenticazione e sessioni. Nessuno ti ringrazia per il refresh token che hai scritto tu.
- Date e fusi orari. Il caso strano ti arriva in produzione, non sulla tua macchina.
- Stili. Un sistema di classi tuo comincia più pulito e finisce per essere un framework peggio documentato.
Il segnale comune è questo: se la parte difficile non è la prima schermata ma l'interazione tra pezzi che non vedi all'inizio, vince la libreria. Se la parte difficile è la tua logica di business, non ce n'è.
Come decido oggi
Ho cambiato l'ordine. Adesso parto dalla libreria ed esco da lì solo se mi blocca.
Un editor collaborativo che mi serviva l'estate scorsa si è risolto così: partire da un template di PlateJS e aggiungere Yjs sopra. Tre giorni dopo il primo commit c'erano già cursori remoti di altri utenti sullo schermo. Mesi prima avrei cominciato dal Block con cinque prop. Per uno strumento di note in Markdown l'anno scorso non mi sono nemmeno posto il problema dell'editor: CodeMirror, un import di highlight.js nel CSS, e via.
E c'è un caso che va ancora più veloce. Ad aprile 2025 ho scritto un agente con strumenti, un agent.ts con i suoi tool di contesto. Il giorno dopo c'è un commit che lo elimina e usa lo streaming del provider direttamente. Meno di ventiquattro ore tra costruirlo e cancellarlo, e quello è il risultato buono.
Il costo vero
Costruirlo a mano non è buttare via il tempo: io so cosa fa l'incollaggio in un editor perché l'ho reimplementato male. Quella conoscenza non si compra leggendo la documentazione.
Il costo sta in quando lo scopri. Tre settimane di blocchi a mano e altre sei di convivenza tra block/ e block2/ fanno due mesi. La prima versione di una mappa in 3D è durata dieci giorni prima che un branch chiamato map-v2 la sostituisse per intero; lì il costo sono stati dieci giorni, non due mesi, perché ho tagliato prima.
La differenza tra le due non è quanto sapevo all'inizio. È quanto ci ho messo ad accettare che sapevo già abbastanza.
Costruirlo a mano è un modo caro di leggere la documentazione. A volte vale la pena pagarlo; quello che non vale la pena è pagarlo due volte per la stessa cosa.
