Así codeamos con Claude · 06: polish e ideas extra
Capítulo 5 cerró con el MVP completo: pintás, deshacés, exportás, recarga la página y sigue. Suficiente para llamarlo terminado. Pero hay esos detalles que separan "MVP" de "se siente como un producto". Ese es este capítulo. La estrella es el time travel.
Time travel
El history ya existía: cada trazo iba a un stack de hasta 100 snapshots con ⌘Z para deshacer. El problema: si querés volver veinte trazos atrás, son veinte teclazos. Y si te pasaste y querés re-avanzar diez sin saber exactamente dónde, andás a ciegas.
La idea del time travel resuelve eso: una bar flotante con thumbnails de cada snapshot, donde podés saltar directo a cualquier estado o previsualizar uno antes de comprometerte.
Pero meter una bar permanente abajo del canvas le robaba espacio al dibujo y le agregaba ruido visual al editor. Solución: la bar está oculta por defecto, fuera del viewport, y un botón ghost mínimo la dispara.
time travel abajo del canvas. la bar de history está oculta fuera del viewport.El botón es deliberadamente discreto. Mono font lowercase, dashed border, color subtle. Ocupa lo mínimo y aparece solo cuando le pasás el mouse por encima.
El cierre con gracia
Una vez abierto el timeline, lo lógico sería cerrarlo apenas el cursor se aleja. Pero eso castiga al usuario: si te alejás un milímetro de un thumb para apuntar a otro, la bar desaparece y arruina la interacción.
Solución: cuando salís de la bar, arranca un timer de 2 segundos completos. Si volvés a entrar antes, el timer se cancela y la bar sigue ahí. Si pasan los 2s sin actividad, slide down y el botón ghost vuelve a aparecer.
Dos segundos quedó bien. Menos te corre, más te hace dudar si todavía está.
El preview que vende el feature
Lo que diferencia el time travel del undo/redo plano es que hacer hover sobre un thumb muestra ese estado en el canvas grande, sin commitear. Podés mirar versiones antiguas, comparar, y si te gusta una hacés click para saltar ahí.
Por detrás es simple: el store del editor tiene un previewIndex: number | null. Cuando es null, el canvas renderiza el sprite actual. Cuando tiene un valor, el SpriteCanvas usa los pixels de history[previewIndex] en vez del estado compuesto. Hover seteas, leave nulleás. Si arrancás un trazo nuevo, el preview se limpia automático.
Lo demás del polish
Tres ajustes chicos que no merecen capítulo propio pero le suman al feel general.
Theme toggle light / dark. Botón sun/moon en la toolbar. Respeta prefers-color-scheme del OS por default, después del primer click se queda con tu elección. Persiste en localStorage. Un script inline en <head> aplica el theme guardado antes del primer paint para evitar el flash de tema equivocado.
user-select: none donde corresponde. Antes, intentar dibujar rápido a veces seleccionaba el texto de los labels del toolbar o del sidebar. Apliqué user-select: none al root del editor y un override text solo para inputs y contenteditable. Pintás sin que el browser intervenga.
Mobile y a11y. En viewports angostos (≤ 640px) la sidebar pasa a una bar horizontal abajo del canvas y los swatches crecen para dedo. Saqué los textos del toolbar para que las acciones quepan. Y un audit con Lighthouse: empezó en 95, lo cerré en 100/100 después de bumpear contraste en dark y alinear los aria-labels al patrón label-in-name de WCAG.