Una decisión de UX que hice mal la primera vez
Esto empezó como un bug pequeño de funcionalidad faltante: tocar una tarjeta de lanzamiento en Discover no hacía nada. La solución en sí terminó siendo simple, pero primero construí la interacción equivocada, y esa es la parte más interesante.
Problema
Lo encontré mientras verificaba en el dispositivo la corrección de otro bug: las tarjetas de lanzamiento de la pestaña Discover eran puro display, sin manejador de toque, sin ninguna acción de agregar. Revisé el código y el propio comentario de documentación de la tarjeta lo delataba, literalmente decía que la acción de agregar a lista de reproducción estaba planeada para un issue anterior, junto con el gesto de deslizar “Not Interested.” Solo se construyó la mitad de eso.
Por qué este enfoque (intento uno)
La app ya tenía un método de servicio que hacía exactamente esto, agregar un álbum del catálogo a una lista de reproducción, sin necesidad de adaptación porque los elementos del feed de Discover ya son del tipo correcto. Conectarlo fue la parte fácil. La pregunta de diseño interesante era la interacción: ¿cómo dispara realmente esta acción quien usa la app?
Mi primer instinto fue buscar consistencia con el gesto “Not Interested” que ya existía en la misma tarjeta: agregar una segunda acción de deslizar, en el borde opuesto, con tinte verde e ícono de más. Calzaba con el modelo de interacción existente de la fila, no requería jerarquía de vistas nueva, y funcionaba, lo construí, escribí pruebas, lo instalé en un dispositivo físico y confirmé que funcionaba de principio a fin.
Contratiempos
Después lo usé de verdad por un tiempo, y estaba mal. En todas partes del resto de la app, Playlist, History, la acción equivalente funciona de la misma manera: se toca una tarjeta y aparece una hoja de detalle con botones. Discover pasó a ser la única pestaña donde había que saber que tocaba deslizar en vez de tocar. Optimicé para “el diff más pequeño en esta tarjeta” y me perdí la consistencia más importante: hacer que calzara con cómo el resto de la app ya manejaba esta misma interacción.
Esa es una lección real sobre encuadrar una decisión de UI desde el punto de referencia equivocado. Comparé la nueva acción contra su hermana en la misma tarjeta en vez de contra su contraparte en las otras pestañas. Ambos son argumentos legítimos de “calza con los patrones existentes,” pero solo uno de los dos sobrevive al uso real de la app, y la única forma de descubrirlo era usarla, no discutirlo desde el diff.
Implementación (intento dos)
Lo reconstruí bien: una hoja de detalle nueva y un view model nuevo, calcando deliberadamente casi línea por línea los dos que ya existen para Playlist y History, el mismo layout de encabezado (portada, título, artista, insignia, año), el mismo enum AddState (idle/adding/done/failed) que maneja el texto, ícono y tinte del botón, el mismo cableado de .sensoryFeedback para la confirmación háptica en el instante en que cambia el estado. La versión de Discover es más simple que sus hermanas porque no necesita una sección de sugerencias del mismo artista, todo el feed de Discover ya es las sugerencias.
Un hallazgo incidental mientras hacía esto: el código existente de agregar a lista de reproducción de History y mi código nuevo de Discover necesitaban una lógica idéntica para resolver la lista de reproducción vinculada y traducir un fallo a un mensaje accionable (“vincula una lista de reproducción en Settings,” “esta lista de reproducción no es tuya para editar,” y así). En vez de copiar y pegar por segunda vez, lo extraje a un pequeño archivo compartido que ambas funciones llaman ahora. Algo pequeño, pero del tipo de duplicación fácil de justificar saltarse en un diff de dos líneas, y mucho más difícil de justificar una vez que se ve el mismo bloque apareciendo dos veces.
Resultados
La suite completa quedó verde con 171 pruebas, incluyendo cobertura nueva para las tres ramas reales del view model de detalle de lanzamiento (agregar exitoso, fallo por lista de reproducción no vinculada, cerrar y descartar). Reconstruí e instalé de nuevo en un dispositivo físico por segunda vez y confirmé que la interacción real calza exactamente con Playlist y History.
Todo el desvío costó unos veinte minutos extra. Valió la pena, lanzar el patrón de interacción equivocado a una superficie de UI compartida es mucho más caro de notar y deshacer después que atraparlo durante una pasada en el dispositivo el primer día.
Lecturas relacionadas
Cuando el mismo ícono significa dos cosas distintas, y por qué la solución no fue solo cambiar una etiqueta
Dos botones de pulgar hacia abajo, con el mismo estilo rojo: uno permanente, uno inofensivo. El lenguaje visual hacía una promesa que el código no cumplía, y el nombre del método era parte del bug.
Unificando "Dismissed" vs "Not Interested," y la fila de Settings que repetía su propio encabezado de sección
Cuatro nombres para un mismo concepto, y el descubrimiento de que la solución obvia habría introducido una nueva inconsistencia un nivel más arriba, más la única repetición que es una función de seguridad, no un error.
La configuración que funcionaba perfecto y se sentía completamente rota
Un filtro por defecto que solo se aplicaba al reabrir la app, porque un TabView mantiene vivos los view models y el estado sembrado en la construcción queda congelado. La solución eliminó un concepto.