Toast y deshacer para la acción de vault de Greenhouse
La acción de vault en Greenhouse solía ser del tipo un clic y sin vuelta atrás. Se hace clic en Vault, la tarjeta desaparece, y eso es todo. En la pantalla de detalle del proyecto, Vault está justo al lado de “Trabajé en esto” y “Mostrar en Finder,” así que un clic equivocado es realista. Recuperarlo significaba abrir la vista de Vault, encontrar el elemento, y hacer clic en Rescatar. No era difícil, pero tampoco obvio, y la app no daba ningún feedback de que algo hubiera pasado.
La solución fue un pequeño sistema de toast: al enviar algo al vault, aparece una tarjeta al final de la pantalla diciendo “X se movió al vault” con un botón Deshacer. Al hacer clic en Deshacer, se llama al mismo comando rescue_item que ya usa la vista de Vault. Nada nuevo del lado de Rust, esta fue una adición puramente de frontend.
Dos cosas hicieron que fuera menos trivial que “agregar un div.”
Primero, esta es la primera superficie de feedback transitorio de la app, de cualquier tipo. Todos los banners existentes en el código son un role="alert" que aparece cuando algo falla. No había precedente para un toast de éxito, ni ninguna región aria-live en ningún lado. Estuve a punto de montar el toast como un componente autocontenido con su propio role="status", renderizado condicionalmente solo cuando hay un mensaje que mostrar. Eso está mal: una región aria-live solo anuncia cambios en un nodo que ya existe en el DOM. Si se crean la región y su contenido en el mismo instante, los lectores de pantalla nunca obtienen el estado “anterior” contra el cual comparar, así que el anuncio simplemente no se dispara. La solución es mantener un host persistente, siempre renderizado, role="status" aria-live="polite" en el dashboard (vacío cuando está inactivo), y montar el contenido real del toast adentro. El host nunca se desmonta, solo cambian sus hijos.
Segundo, uno de los tres lugares desde donde se puede enviar algo al vault es la vista de detalle del proyecto, que tiene una función de renombrado en línea. Si se renombra un proyecto y luego se lo envía al vault en la misma visita, ¿qué nombre debería mostrar el toast? La vista se cierra a sí misma justo después de enviar al vault, así que no puede retener el estado del toast, tiene que pasarle el mensaje al dashboard mediante un callback. Mi primer intento pasaba el nombre con el que se había abierto originalmente la vista. Una pasada de revisión detectó que esto queda desactualizado apenas se renombra: enviar algo al vault justo después de un renombrado haría que el toast mostrara el nombre viejo. La solución real lee el estado actual del detalle en el momento de enviar al vault, no el prop con el que se creó la vista.
La verificación acá importó más de lo habitual porque “¿se dispara el anuncio?” y “¿Deshacer realmente restaura el elemento?” no son cosas que una prueba de componente por sí sola demuestre de forma convincente. Corrí todo el flujo a través del harness real de WebDriver de Greenhouse: capturé una idea real, hice clic en el botón Vault real, confirmé que el toast y Deshacer se renderizaban dentro del host aria-live real, hice clic en Deshacer, y confirmé que el elemento realmente volvía mediante una ida y vuelta de IPC real a rescue_item, no un mock.
Es una función pequeña, pero un buen recordatorio de que “solo agregar un toast” tiene un par de trampas reales escondidas: la regla de timing del DOM para regiones live, y la desactualización de props cada vez que una vista hija puede sobrevivir a los datos con los que se abrió.
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.
Arreglar una carrera de concurrencia, un bug de la tecla Esc, y un crash por clave duplicada en Greenhouse
Un contador monotónico para refrescos fuera de orden, un evento de diálogo cancelable, una clave de timestamp que choca dentro del mismo segundo, y una prueba de regresión que no podía fallar.
Arreglar el error de manejo de foco de Greenhouse me enseñó la diferencia entre un diálogo y una página
El ticket decía que había que restaurar el foco al elemento que lo disparó. Ese elemento ya no existe, se destruyó en el instante en que se abrió la vista. El patrón correcto era el de una navegación de página, no el de un modal.