El diálogo que se cerró solo antes de poder usarlo
Greenhouse tenía un vacío que llevaba semanas esperando en silencio en el backlog: una vez que se apuntaba la app a una carpeta durante la configuración, eso era todo. No había forma de cambiar de opinión después, salvo borrar la configuración de la app y empezar de nuevo. Por fin llegué a eso esta semana, y resultó ser una de esas tareas que parecen puro trabajo mecánico hasta que dejan de serlo.
La reutilización que parecía obvia
Unas semanas antes había construido la pantalla de incorporación que permite apuntar Greenhouse a una carpeta existente llena de proyectos viejos, escanearla, y decidir qué incorporar. Esa pantalla ya tenía todo lo que necesitaría una función de “cambiar de bóveda”: un selector de carpetas, un escáner, una lista de asignación. Así que el plan era simple: extraer esa lógica en una pieza reutilizable propia, y construir un pequeño diálogo alrededor para el cambio.
La extracción en sí salió limpia. Lo que no salió limpio fue una sola palabra: qué significa “listo”.
Un callback con una opinión oculta
La pantalla de incorporación original tenía una regla específica: si ya se había elegido una carpeta y resultaba que no quedaba nada por clasificar, se desbloqueaba el botón “Siguiente” para poder avanzar. Tenía sentido: es un asistente de configuración lineal, y no hay nada que hacer ahí, así que se deja continuar a la persona.
Al llevar esa lógica a una pieza compartida, renombré la señal de algo específico del asistente a algo genérico, simplemente “llamar a esto cuando no quede nada por decidir”. Sonaba razonable. Conecté el nuevo diálogo de cambio de bóveda para que se cerrara solo cada vez que se disparara esa señal.
Después abrí el diálogo de verdad. Se cerró solo al instante. Todas las veces. Antes de poder hacer clic en nada.
Lo que se me había pasado: la pieza compartida sigue revisando su carpeta inicial en el momento en que aparece, exactamente como hacía antes el paso del asistente. En la incorporación, ver “nada que hacer aquí” sobre una carpeta ya configurada significa “está listo, se puede avanzar”. En un diálogo cuyo propósito entero es “tal vez elegir algo distinto”, ver “nada que hacer aquí” sobre la carpeta en la que ya se está simplemente significa que el diálogo se abrió. Son dos situaciones completamente distintas que resultaron producir exactamente la misma señal, y supuse que el significado de la señal viajaba con ella al mover el código. No fue así. El código era idéntico, la frase que le decía a dos oyentes distintos significaba dos cosas distintas.
Arreglarlo sin hacer que la pieza compartida adivine
La solución tentadora es hacer más inteligente al componente compartido, darle una bandera como “¿debo cerrarme solo cuando la carpeta inicial ya está resuelta?”. Eso no era lo que quería. En el momento en que una pieza reutilizable empieza a preguntarse “bueno, ¿en qué situación estoy?”, el criterio queda empujado adentro de la caja, donde es más difícil de ver y más fácil de equivocar la próxima vez que alguien la reutilice.
En cambio, dejé la pieza compartida exactamente tan directa como siempre fue: sigue anunciando “no queda nada por decidir” cada vez que eso es cierto, sin excepciones, sin sensibilidad al contexto. El diálogo en sí ahora rastrea una sola cosa simple: ¿la persona eligió algo nuevo de verdad desde que se abrió? Solo si eso es cierto se trata “no queda nada por decidir” como “aquí ya se terminó”. De lo contrario, el anuncio queda… registrado, e ignorado. El núcleo tonto y predecible siguió siendo tonto y predecible. El criterio se movió al único lugar que realmente tiene contexto suficiente para tomarlo: quien llama, no la herramienta.
Qué me hizo confiar en la solución
Este es exactamente el tipo de error que un verificador de tipos que pasa nunca va a detectar, porque nada de los tipos cambió: un callback seguía disparándose, en el momento correcto, con la firma correcta. El desajuste estaba enteramente en lo que se suponía que ese disparo significaba para quien estuviera escuchando. Así que la prueba que escribí no era “¿funciona hacer clic en las cosas?”, sino específicamente “abrir este diálogo contra una carpeta que ya está completamente resuelta, y confirmar que nada se cierra”. Ese es el único escenario en el que la vieja suposición habría reaparecido en silencio si la solución hubiera quedado apenas un poco mal, y es exactamente el escenario que un recorrido manual rápido nunca habría sacado a la luz, porque a quien probara manualmente se le tendría que ocurrir intentar a propósito el camino de “no hacer nada”.
Lecturas relacionadas
Rediseño de navegación: pestañas en la barra superior y un diálogo de Settings
Ocho controles de footer con el mismo peso visual, una clave de i18n cumpliendo doble función, y el patrón ARIA que el propio test de a11y del proyecto no dejó construir a medias.
Seis correcciones pequeñas de UX en una sola sesión
Límites de layout para pantallas ultra anchas, timestamps que respetan el idioma, emojis convertidos a SVG, errores descartables, un hero de primer uso, y la palabra que aparecía tres veces en una sola tarjeta.
Toast y deshacer para la acción de vault de Greenhouse
La primera superficie de feedback transitorio de la app, la regla de aria-live que hace que un toast renderizado condicionalmente quede sin anunciar en silencio, y un prop que queda desactualizado apenas se renombra algo.