Ask HN: En tu experiencia, ¿cuáles son las convenciones sólidas para el desarrollo de UI de tinta electrónica?
Ask HN: In your experience, what are sound conventions for e-ink UI development?
Estoy trabajando en un port de Excalidraw a mi ReMarkable Pro y me he dado cuenta de cuánto damos por sentado en las interacciones de UI cuando asumimos una pantalla de 30+ Hz con poco o ningún ghosting. El diseño debe volverse lo más monotónico posible, especialmente en ausencia de interacción del usuario; en otras palabras, la aplicación nunca debería hacer nada que interrumpa los renders anteriores por sí sola a menos que estés limpiando toda la página. Cuando es el usuario quien interactúa, intenta minimizar el número de estados intermedios que producen sus interacciones. Esto no significa 'simplemente no renderices esos estados intermedios', sino que las interacciones en sí no requieran retroalimentación intermedia. Básicamente, los usuarios (y desarrolladores) están tan acostumbrados a manipular cosas sin efectos secundarios que tienes que hacer que cada interacción posible sea lo más intencional posible, dejándolos menos frustrados cuando ven un refresco. Si una operación va a causar un refresco, debería ocurrir en un momento que el usuario ya entienda como un límite semántico. Obviamente no siempre puedes evitar esto, así que cuando haya un requisito duro de retroalimentación continua, usa una representación transitoria degradada/barata y refresca al confirmar esa acción. Con E-Ink, cada interacción tiene que ser un equilibrio entre ghosting y latencia. Un patrón común al que volví es estados intermedios que optimizan la latencia a expensas del ghosting hasta que la acción de UI esté 'completa'. Esto permitirá que los refrescos se asocien con la satisfacción de la finalidad. Curiosamente, muchos se remontan a viejos paradigmas de UI de cuando los gráficos por computadora no eran tan potentes como hoy. Al redimensionar, simplemente dibujo el contorno de la forma con un modo de actualización rápida que tolera más ghosting hasta que el usuario suelta el dedo o el lápiz, momento en el cual, después de un pequeño retraso para tener en cuenta un 'ups, solo un poco más grande/más pequeño', hago un refresco localizado de la unión del área anterior y la nueva. El contorno en sí es deliberadamente tenue, para que el refresco no tenga que ser tan intenso si el delta es pequeño. Para tu caso de streaming de LLM, recomendaría hacerlo en fragmentos, como dijiste, quizás haciendo un refresco completo de todo excepto la caja de entrada + moviendo la cola del último mensaje enviado + el comienzo del stream hacia arriba al enviar. Por supuesto, esto implica que sabes exactamente cuánto durará la respuesta, lo cual no sabes. El desafío será idear un paradigma de UI que no se vea incómodo si la respuesta solo baja hasta la mitad de la pantalla, y que también haga un refresco limpio de manera preventiva si está claro que se desbordará y necesitará moverse hacia arriba. Para el primer caso, encuentra alguna forma de que no se vea completamente incómodo si es parcial, y luego haz un refresco del área del historial de chat y muévelo a su posición adecuada una vez que el usuario haga clic en la caja de entrada para escribir su siguiente respuesta. Para el segundo, quizás incrustar algún tipo de medidor de espacio restante en la parte inferior que indique cuándo necesitarías hacer esta acción de mover + refrescar (no lo etiquetes directamente así) permitiría al usuario anticiparlo más y estar menos frustrado cuando ocurra, ya que psicológicamente están esperando la siguiente parte de la respuesta/razonamiento, no sorprendidos por un flash repentino. Para el streaming real, tendrás que asegurarte de que la alineación del texto funcione de tal manera que una vez que una línea se renderiza en la pantalla, su colocación sea final (en otras palabras, sin streaming intra-palabra, ya que necesitas saber si la palabra cabrá en el espacio restante de la línea antes de renderizarla). Básicamente, evita el reflow retroactivo como un documento PDF con diseño fijo, no como esta caja de texto en la que estoy escribiendo con un controlador de redimensionamiento en la parte inferior derecha. También probablemente querrás deshabilitar el desplazamiento durante la generación, o al menos hacer algún mecanismo de paginación discreto para ir y venir. En cualquier caso, espero ver lo que estás construyendo.
Si lo descubres, ¡dímelo! He estado trabajando en un port de Excalidraw a mi ReMarkable Pro y me ha abierto los ojos a cuánto damos por sentado en términos de interacciones de UI cuando asumimos que tenemos una pantalla de 30+ hz con poco o ningún ghosting. El diseño tiene que volverse lo más monotónico posible, especialmente en ausencia de interacción del usuario; en otras palabras, la aplicación en sí nunca debería hacer nada que interrumpa los renders anteriores por sí sola a menos que estés limpiando toda la página. Cuando es el usuario quien interactúa con la aplicación, intenta minimizar el número de estados intermedios que producen sus interacciones. Esto NO significa 'simplemente no renderices esos estados intermedios', sin embargo. Significa hacer que las interacciones en sí no requieran retroalimentación intermedia. Esencialmente, los usuarios (y desarrolladores) están tan acostumbrados a simplemente poder manipular cosas sin efectos secundarios que tienes que hacer que cada interacción posible sea lo más intencional posible, dejándolos menos frustrados cuando ven un refresco. Si una operación va a causar un refresco, debería ocurrir en un momento que el usuario ya entienda como un límite semántico. Obviamente no siempre puedes evitar esto, así que cuando haya un requisito duro de retroalimentación continua, usa una representación transitoria degradada/barata y refresca al confirmar esa acción. Con E-Ink, cada interacción tiene que ser un equilibrio entre ghosting y latencia. Un patrón común al que volví es estados intermedios que optimizan la latencia a expensas del ghosting hasta que la acción de UI esté 'completa'. Esto permitirá que los refrescos se asocien con la satisfacción de la finalidad. Curiosamente, muchos se remontan a viejos paradigmas de UI de cuando los gráficos por computadora no eran tan potentes como hoy. Al redimensionar, simplemente dibujo el contorno de la forma con un modo de actualización rápida que tolera más ghosting hasta que el usuario suelta el dedo o el lápiz, momento en el cual, después de un pequeño retraso para tener en cuenta un 'ups, solo un poco más grande/más pequeño', hago un refresco localizado de la unión del área anterior y la nueva. El contorno en sí es deliberadamente tenue, para que el refresco no tenga que ser tan intenso si el delta es pequeño. Para tu caso de streaming de LLM, recomendaría hacerlo en fragmentos, como dijiste, quizás haciendo un refresco completo de todo excepto la caja de entrada + moviendo la cola del último mensaje enviado + el comienzo del stream hacia arriba al enviar. Por supuesto, esto implica que sabes exactamente cuánto durará la respuesta, lo cual no sabes. El desafío será idear un paradigma de UI que no se vea incómodo si la respuesta solo baja hasta la mitad de la pantalla, y que también haga un refresco limpio de manera preventiva si está claro que se desbordará y necesitará moverse hacia arriba. Para el primer caso, encuentra alguna forma de que no se vea completamente incómodo si es parcial, y luego haz un refresco del área del historial de chat y muévelo a su posición adecuada una vez que el usuario haga clic en la caja de entrada para escribir su siguiente respuesta. Para el segundo, quizás incrustar algún tipo de medidor de espacio restante en la parte inferior que indique cuándo necesitarías hacer esta acción de mover + refrescar (no lo etiquetes directamente así) permitiría al usuario anticiparlo más y estar menos frustrado cuando ocurra, ya que psicológicamente están esperando la siguiente parte de la respuesta/razonamiento, no sorprendidos por un flash repentino. Para el streaming real, tendrás que asegurarte de que la alineación del texto funcione de tal manera que una vez que una línea se renderiza en la pantalla, su colocación sea final (en otras palabras, sin streaming intra-palabra, ya que necesitas saber si la palabra cabrá en el espacio restante de la línea antes de renderizarla). Básicamente, evita el reflow retroactivo como un documento PDF con diseño fijo, no como esta caja de texto en la que estoy escribiendo con un controlador de redimensionamiento en la parte inferior derecha. También probablemente querrás deshabilitar el desplazamiento durante la generación, o al menos hacer algún mecanismo de paginación discreto para ir y venir. En cualquier caso, espero ver lo que estás construyendo.