02Criteri5 min

El que sempre acaba passant quan començo una cosa a mà

He construït editors a mà quatre vegades. Tres van acabar reescrits amb una llibreria. La quarta no va arribar als dos dies.

Per Salva Castells

Imatge de portada: Globetrotter19, CC BY-SA 4.0.
En aquest post

El setembre del 2024 vaig passar tres setmanes construint un editor de blocs. La idea era la de sempre: cada paràgraf és un bloc, l'arrossegues, el reordenes, i cada bloc té el seu propi botó d'IA per reescriure'l. Res d'exòtic. Notion fa anys que ho fa.

El vaig muntar amb dnd-kit per a l'arrossegament i una instància d'editor per bloc. El component Block rebia id, index, content, onDelete, onContentChange, una funció regenerateContent amb cinc paràmetres, la llista sencera de blocs per al context, i un AbortController en un ref per tallar la generació a mig fer. Funcionava. Un dels commits d'aquella setmana es diu literalment "never give up!!". Això és el que va quedar escrit.

Sis setmanes després tot allò estava esborrat i al seu lloc hi havia un sol editor amb extensions.

Què es trenca quan tu orquestres els blocs

El problema no era l'arrossegament. L'arrossegament va sortir bé. El problema era tota la resta, i ho vaig anar descobrint en aquest ordre:

  • Sincronitzar contingut cap endins. Cada bloc tenia un useEffect comparant content !== editor.getHTML() per cridar setContent. Comparar HTML com a cadena per decidir si l'estat ha canviat és exactament la mena de cosa que sembla que funciona fins que no.
  • Selecció entre blocs. No existeix. Si cada bloc és el seu propi editor, l'usuari no pot seleccionar des de la meitat d'un paràgraf fins a la meitat del següent. No hi ha arreglada parcial: o hi ha un document o hi ha N documents.
  • Desfer. Cada instància té la seva pila d'historial. Ctrl+Z desfà al bloc on hi ha el cursor, no l'última cosa que vas fer. Ningú no espera això.
  • Enganxar. Enganxes tres paràgrafs en un bloc i es queden tots tres dins d'un bloc. Partir-los és reimplementar a mà el que l'esquema del document ja sap fer.

Cap d'aquestes quatre coses no era al meu cap quan vaig començar. Totes quatre van aparèixer en el mateix ordre en què les he escrit.

La reescriptura va ser més curta que el prototip

A mitjan octubre la carpeta block/ ja convivia amb una carpeta block2/ que tenia tres fitxers: l'editor, la barra d'eines i un hook. El hook sencer eren quaranta línies: StarterKit, TextStyle, FontFamily, TextAlign, Highlight, Placeholder. Sis extensions declarades en un array, i amb això venien l'historial unificat, la selecció contínua i l'enganxat amb esquema. Al novembre van arribar les llistes de tasques, els blocs de codi i les imatges per URL: un import i una entrada a l'array cadascuna.

El nom de la carpeta ho diu tot. block2 no és una refactorització. És reconèixer que el que hi ha al costat no se salva.

El mateix amb el CSS d'aquest lloc. Tenia un fitxer de mil sis-centes vuitanta-una línies amb classes pròpies. En passar-lo a components, l'única cosa que va sobreviure van ser els tokens de color: cent tretze línies a globals.css. La resta era reimplementació.

On la llibreria guanya sempre

No és "fes servir llibreries". És que hi ha categories concretes on el problema és més gran del que sembla des de fora, i són sempre les mateixes:

  • Editors de text enriquit. Selecció, historial, enganxat i esquema són un sol sistema. No en pots agafar tres de quatre.
  • Autenticació i sessions. Ningú no t'agraeix el refresh token que vas escriure tu.
  • Dates i fusos horaris. El cas rar t'arriba en producció, no a la teva màquina.
  • Estils. Un sistema de classes propi comença sent més net i acaba sent un framework pitjor documentat.

El senyal comú és aquest: si la part difícil no és la primera pantalla sinó la interacció entre peces que no veus en començar, la llibreria guanya. Si la part difícil és la teva lògica de negoci, no n'hi ha.

Com decideixo avui

Vaig canviar l'ordre. Ara arrenco amb la llibreria i només en surto si em bloqueja.

Un editor col·laboratiu que necessitava l'estiu passat es va resoldre així: partir d'una plantilla de PlateJS i afegir-hi Yjs a sobre. Tres dies després del primer commit ja hi havia cursors remots d'altres usuaris a la pantalla. Mesos abans hauria començat pel Block amb cinc props. Per a una eina de notes en Markdown l'any passat ni em vaig plantejar l'editor: CodeMirror, un import de highlight.js al CSS, i a la meva.

I hi ha un cas que va encara més ràpid. L'abril del 2025 vaig escriure un agent amb eines, un agent.ts amb les seves tools de context. L'endemà hi ha un commit que l'elimina i fa servir el streaming del proveïdor directament. Menys de vint-i-quatre hores entre construir-lo i esborrar-lo, i aquest és el resultat bo.

El cost real

Construir-ho a mà no és llençar el temps: jo sé què fa l'enganxat en un editor perquè l'he reimplementat malament. Aquest coneixement no es compra llegint documentació.

El cost és en quan ho descobreixes. Tres setmanes de blocs a mà i sis més de convivència entre block/ i block2/ són dos mesos. La primera pàgina d'un mapa en 3D va durar deu dies abans que una branca anomenada map-v2 la substituís sencera; allà el cost van ser deu dies, no dos mesos, perquè vaig tallar abans.

La diferència entre les dues no és quant sabia en començar. És quant vaig trigar a acceptar que ja en sabia prou.

Construir-ho a mà és una manera cara de llegir la documentació. De vegades val la pena pagar-la; el que no val la pena és pagar-la dues vegades pel mateix.