El editor que se comía mis <
Este es un bug que se sentía como gaslighting: como si algo te hiciera dudar de lo que veías con tus propios ojos. Abrías el editor de plantillas de
SendDock, cambiabas a la pestaña de Código, y escribías un signo de menor que —
un < solo. En el instante en que lo escribías, se convertía en <. Escribías
<div> y lo veías deshacerse en <div> un carácter a la vez, como si algo
estuviera transcribiendo tu HTML a entidades en tiempo real. Que, resultó, era
exactamente lo que pasaba.
Dos editores, un solo valor
El editor de plantillas tiene dos pestañas sobre el mismo contenido: una pestaña
de Código respaldada por CodeMirror, y una pestaña Visual respaldada por
GrapesJS, el constructor drag-and-drop. Ambas están enlazadas al mismo v-model
— el HTML de la plantilla. Escribes en una, la otra lo refleja. Ese valor
compartido es todo el punto, y también todo el problema.
La pestaña Visual estaba oculta con v-show. Y v-show no desmonta — solo pone
display: none. Así que mientras escribías tranquilo en la pestaña de Código, la
instancia de GrapesJS en la pestaña Visual invisible seguía montada, seguía viva,
seguía observando.
El loop de retroalimentación
Esta es la secuencia, por cada tecla:
- Escribes
<en CodeMirror. Elv-modelcompartido se vuelve el string<. - GrapesJS, observando ese modelo, dispara su watcher y llama a
setComponents()con el valor a medio escribir. <por sí solo no es HTML válido. GrapesJS hace algo razonable para un parser al que le pasan basura: trata al<solo como contenido de texto. Y el contenido de texto, serializado de forma segura de vuelta a HTML, es<.- GrapesJS reemite
<de vuelta alv-modelcompartido. - CodeMirror, enlazado a ese mismo modelo, ahora muestra
<.
Cada tecla daba una vuelta completa por un parser de HTML oculto que "amablemente" escapaba todo lo que aún no fuera markup válido. No estabas escribiendo en un editor. Estabas escribiendo en dos editores jugando al teléfono roto, y uno de ellos no hablaba HTML a medio terminar.
El arreglo es una letra
v-show se volvió v-if.
Con v-if, la instancia de GrapesJS de la pestaña Visual ni siquiera está montada
a menos que estés realmente en esa pestaña. Sin componente oculto, sin watcher,
sin vuelta completa, sin escapado. La pestaña de Código vuelve a ser solo un
editor de código.
Ese es el arreglo entero. Una letra — que es lo molesto y lo instructivo del
asunto. El bug no estaba en el código que se veía roto (CodeMirror hacía su
trabajo a la perfección) sino en un componente dos pestañas más allá que todos
habían archivado mentalmente como "no está en pantalla, no está corriendo".
v-show violaba esa suposición en silencio.
La lección
"Oculto" no es "ido". v-show, opacity: 0, visibility: hidden, una ruta
fuera de pantalla mantenida viva por velocidad — todas dejan al componente
corriendo, con sus watchers, timers y suscripciones plenamente vivos. Si dos
componentes comparten una fuente de verdad y uno de ellos transforma esa verdad
al cambiar, no importa que sea invisible. El código invisible igual corre. Y
cuando corre sobre un valor que otro componente está editando activamente,
obtienes un bug que te responde escribiendo.
Cuando algo en pantalla se comporta de forma imposible, el culpable suele ser algo fuera de pantalla que nunca dejó de trabajar.
Notas relacionadas
El servidor que se reiniciaba cada cinco horas
Un contenedor que se crasheaba en loop, sin panic y sin logs — cuatro veces, por cuatro razones distintas. Un recorrido por todas las formas en que un health check te puede mentir.
Que el envío sobreviva a un reinicio
La primera versión mandaba toda la campaña desde una sola goroutine — y lo perdía todo si el proceso parpadeaba. Reconstruirla como una cola durable por destinatario.