Live browser iteration: el comando live

Por: Artiko
impeccablelivebrowseriteracióndom

Live browser iteration: el comando live

En el capítulo anterior vimos cómo clarify, adapt y optimize cierran la brecha entre el contenido y su contexto de consumo. Todos esos comandos comparten un rasgo: el agente trabaja a ciegas. Lee el código, razona sobre cómo se verá y escribe cambios sin haber mirado nunca el resultado renderizado. Funciona sorprendentemente bien gracias a las 44 reglas deterministas del detector, pero hay un techo: ningún razonamiento estático sustituye a ver la interfaz viva y poder tocarla.

El comando live rompe ese techo. Abre una sesión de iteración en el navegador, en tiempo real: el agente sirve tu UI, te deja seleccionar elementos y editar directamente sobre el DOM, captura esas ediciones como evidencia y finalmente las hace commit de vuelta al código fuente. Es el único comando de Impeccable donde el bucle “ver → editar → confirmar” se cierra con feedback visual real en lugar de inferencia. En este capítulo lo desarmamos pieza por pieza.

Por qué la iteración en vivo cambia las reglas

El diseño de interfaces es, por naturaleza, un proceso de aproximaciones sucesivas. Mueves un padding, miras, mueves de nuevo. Un agente que solo escribe código y espera al siguiente prompt para ver una captura pierde la mitad de la información: no percibe el micro-ajuste que un humano hace con el ratón en dos segundos.

live invierte esa dinámica. En lugar de que el agente proponga y tú revises en otra herramienta, abre un canal bidireccional:

Es la diferencia entre dirigir a alguien por teléfono y trabajar codo con codo frente a la misma pantalla.

sequenceDiagram
    participant U as Usuario (navegador)
    participant S as Live server (live.mjs)
    participant A as Agente (poll loop)
    participant C as Código fuente

    A->>S: Arranca servidor + inyecta picker
    S-->>U: Sirve /live.js sobre la página
    U->>S: Selecciona elemento / edita en el DOM
    S->>A: Evento (generate / manual_edit_apply)
    A->>C: Escribe variantes o aplica ediciones
    A->>S: --reply done (señala fin de trabajo)
    S-->>U: Hot-swap de variantes (HMR)
    U->>S: accept (o discard)
    A->>S: live-accept (persiste la variante, limpia la carbonización)
    A->>C: Reescribe el código fuente limpio
    A->>S: live-complete --id SESSION_ID (acuse de cierre)
    Note over U,C: El bucle ver → editar → commit queda cerrado

Anatomía de una sesión live

Antes de invocar nada conviene entender que live no es un único script, sino una orquesta de procesos pequeños que se coordinan a través de un journal durable en disco. El agente los lanza por ti cuando ejecutas el comando, pero saber qué hace cada uno te ayuda a depurar cuando algo se atasca.

ScriptRol
live.mjsArranca el servidor auxiliar e inyecta el picker en los HTML configurados
detect-csp.mjsIdentifica la forma de la CSP y los puntos a parchear durante el bootstrap
live-poll.mjsBucle de eventos: bloquea esperando acciones del navegador
live-wrap.mjsEnvuelve el elemento seleccionado para alojar variantes
live-insert.mjsModo insertar: andamia contenido nuevo en un punto de anclaje
live-accept.mjsPersiste la variante aceptada y gestiona la limpieza de la carbonización (carbonize)
live-complete.mjsAcuse manual de cierre de sesión tras la carbonización
live-status.mjsReporta el estado de sesiones pendientes
live-resume.mjsRecupera una sesión interrumpida desde el journal
live-server.mjs stopDetiene el servidor y retira la inyección del HTML

Todos viven bajo .claude/skills/impeccable/scripts/. El estado persistente, en cambio, se guarda en .impeccable/live/: la configuración en config.json y cada sesión en sessions/.

El comando que tú escribes

Como en el resto del curso, lo que tecleas es la forma de alto nivel:

/impeccable live src/routes/+page.svelte

El <target> es la página o el archivo que quieres iterar. El agente traduce eso a la secuencia de scripts. Bajo el capó, el arranque equivale a:

node .claude/skills/impeccable/scripts/live.mjs

Ese arranque devuelve tres datos clave que el agente usa durante toda la sesión:

Un detalle importante: el live helper no es tu servidor de aplicación. Tu app sigue corriendo en su propio origen y puerto (Vite, Next, Astro, lo que uses); el helper solo añade encima la capa de edición y el canal de eventos. Son dos servidores conviviendo.

El flujo completo, paso a paso

El orden de las fases es rígido: bootstrap → navegar → bucle de long-poll. No se pueden reordenar. Es una de las limitaciones explícitas del comando, y tiene sentido: el bucle de eventos no existe hasta que el servidor está arriba y la página instrumentada.

1. Bootstrap y configuración

En la primera ejecución, live.mjs te pide configurar .impeccable/live/config.json con los archivos HTML objetivo (acepta patrones glob). Si apuntas a un monorepo, el script resuelve y devuelve projectRoot automáticamente.

Aquí ocurre también la detección de CSP (Content Security Policy), a cargo de un script dedicado, detect-csp.mjs. Si tu app tiene una política estricta, el picker no podría cargar /live.js. Ese script identifica dos formas auto-parcheables y añade una excepción solo en desarrollo para http://localhost:8400:

const __impeccableLiveDev =
  process.env.NODE_ENV === "development" ? ["http://localhost:8400"] : [];

Ese valor se esparce dentro de las directivas script-src y connect-src. Como está guardado tras NODE_ENV === "development", nunca llega a producción. La otra forma (append-string) interpola la variable dentro de una CSP escrita como literal de cadena.

2. El bucle de poll (proceso de larga duración)

Una vez en pie, el agente entra en el bucle:

node .claude/skills/impeccable/scripts/live-poll.mjs

Por defecto, este proceso bloquea hasta 600 segundos por evento y sale tras manejarlo. Existe un modo --stream que mantiene un único proceso vivo, pero depende de que el stdout incremental sea fiable, algo que no siempre se cumple en todos los clientes; el modo bloqueante por evento es el camino seguro.

Cada vez que tú haces algo en el navegador, llega un evento. El agente despacha según su tipo:

3. Selección de elemento y generación de variantes

Cuando seleccionas un elemento y pides variantes (evento generate), puedes además añadir anotaciones: comentarios o trazos sobre la página. Si hay anotaciones, el agente lee la captura para entenderlas.

El agente envuelve el elemento elegido:

node .claude/skills/impeccable/scripts/live-wrap.mjs --id EVENT_ID --count 3 \
  --element-id "ELEMENT_ID" --classes "class1,class2" --tag "div" --text "TEXT_SNIPPET"

Esto devuelve el punto de inserción, el modo de CSS (scoped o prefijado) y el formato de la etiqueta de estilo. El agente planifica tres variantes distintas dentro de la identidad de marca existente (o en departure mode si el PRODUCT.md declara anti-referencias o tú lo pides), y las escribe en una sola edición:

<style data-impeccable-css="SESSION_ID">
  /* reglas scoped o prefijadas según cssAuthoring.mode */
</style>
<div data-impeccable-variant="1"><!-- variante 1: elemento completo --></div>
<div data-impeccable-variant="2" style="display: none"><!-- variante 2 --></div>
<div data-impeccable-variant="3" style="display: none"><!-- variante 3 --></div>

Cada data-impeccable-variant debe contener exactamente un elemento raíz. El CSS scoped usa el <div> envoltorio como frontera del @scope, así que el agente entra siempre con combinadores descendentes (:scope > .card, :scope .title). Opcionalmente, cada variante puede declarar de 0 a 4 parámetros como JSON en su envoltorio: perillas de ajuste (range, steps, toggle) que tú mueves en un panel lateral sin coste de regeneración, porque el CSS ya está escrito contra ellas.

Existe también un modo insertar (event.mode === "insert"), que usa live-insert.mjs en lugar de envolver, para andamiar contenido nuevo en un punto de anclaje. El modo replace es el predeterminado; insertar requiere un prompt no vacío o anotaciones.

4. Señalar que terminaste

Tras escribir las variantes, el agente avisa al servidor y vuelve a hacer poll de inmediato:

node .claude/skills/impeccable/scripts/live-poll.mjs --reply EVENT_ID done --file RELATIVE_PATH

5. El usuario cicla las variantes y elige

El navegador muestra las tres. Tú ajustas parámetros si están expuestos, comparas y haces clic en aceptar o descartar. Aquí está la magia del feedback visual: no decides sobre una descripción, decides sobre el render real.

6. Aceptar y carbonizar

Cuando aceptas, la variante elegida queda carbonizada: cosida temporalmente en el código con marcadores auxiliares (impeccable-carbonize-start/end) y CSS inline. Eso no es la forma final. El agente debe reescribirla limpia antes del siguiente poll, y es un paso no opcional:

  1. Localizar el bloque de carbonización por sus marcadores.
  2. Mover el CSS inline a la hoja de estilos del proyecto y reapuntar los selectores a clases semánticas.
  3. Hornear los valores de parámetros (eliminar ramas de steps/toggle no elegidas; sustituir o resetear los range).
  4. Desenvolver el contenido aceptado (borrar el envoltorio data-impeccable-variant).
  5. Eliminar marcadores, la etiqueta de estilo inline y las reglas de variantes muertas.

Y se cierra la sesión:

node .claude/skills/impeccable/scripts/live-complete.mjs --id SESSION_ID

7. Recuperación: status y resume

¿Y si el poll se interrumpe o el helper se reinicia a media sesión? Para eso existe el journal durable. Dos comandos lo leen y recuperan el trabajo pendiente:

node .claude/skills/impeccable/scripts/live-status.mjs
node .claude/skills/impeccable/scripts/live-resume.mjs --id SESSION_ID

status te dice qué sesiones hay y en qué estado; resume retoma una concreta. Como el estado vive en .impeccable/live/sessions/, una sesión sobrevive a que se caiga el agente o se cierre el navegador.

8. Salir y limpiar

Al terminar, se detiene el servidor y se retira la inyección del picker del HTML:

node .claude/skills/impeccable/scripts/live-server.mjs stop

Si planeas volver pronto, stop --keep-inject conserva la etiqueta inyectada para un reinicio rápido.

El buffer de ediciones manuales

Hasta aquí hemos visto la generación de variantes. Pero el corazón de este capítulo, y lo que distingue a live de cualquier herramienta de “preview”, es el buffer de ediciones manuales.

Mientras cicleas variantes, puedes hacer cambios a mano sobre la página: corregir un texto, ajustar una estructura, mover algo. Esas ediciones se acumulan en un buffer. Cuando haces clic en Apply, llega un evento manual_edit_apply con el lote de cambios:

El agente aplica esas ediciones al código fuente y responde con un resultado JSON estructurado:

node .claude/skills/impeccable/scripts/live-poll.mjs --reply EVENT_ID done \
  --data '{"status":"done","appliedEntryIds":["id1"],"failed":[],"files":["src/page.html"]}'

Si el aplique fue incompleto, usa status:"partial" o status:"error" con la lista failed[]. Y aquí está la garantía clave: las ediciones previas siguen siendo recuperables hasta que se llama a live-complete.mjs. Es decir, el commit de tus ediciones manuales de vuelta al código no es una operación de fe; queda registrado, asociado a evidencia, y puede reintentarse o descartarse.

Este es el ciclo completo que pide el comando: ver la UI → editar sobre el DOM → capturar evidencia (manual-edit-evidence) → commit al código (live-commit-manual-edits) → o descartar.

Steer: dirección sin selección

No todo requiere seleccionar un elemento. El evento steer es una dirección ligera a nivel de página: escribes (o dictas) en una barra global una instrucción del tipo “haz el hero más respirado” sin apuntar a nada concreto. Tras trabajar, el agente responde:

node .claude/skills/impeccable/scripts/live-poll.mjs --reply EVENT_ID steer_done ["Nota opcional para el toast"]

No necesita acuse de recibo: la barra se desbloquea cuando llega steer_done.

Soporte de frameworks

live se adapta a cómo cada framework renderiza, porque inyectar un picker no es igual en HTML plano que en componentes compilados.

Limitaciones que conviene tener presentes

live es potente, pero tiene fronteras claras. Conocerlas evita frustración:

Los ocho estados que debes iterar

La referencia de interaction design recuerda que iterar en vivo no es solo mirar el estado en reposo. Hay ocho estados que merecen feedback visual y que live te permite recorrer de verdad, no imaginar: default, hover, focus, active, disabled, loading, error y success.

Un error clásico es diseñar el hover y olvidar el focus, lo que deja a los usuarios de teclado sin pistas. La pauta es usar :focus-visible con un anillo de alto contraste:

button:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

Contraste mínimo de 3:1 contra los colores adyacentes y grosor de 2 a 3px. Y nunca quitar outline: none sin reemplazarlo: es una violación directa de accesibilidad. Para overlays como menús y dropdowns, prefiere posicionamiento moderno (position: fixed, CSS Anchor Positioning o la Popover API) para no quedar recortado dentro de contenedores con overflow: hidden. Iterar en vivo es precisamente el contexto donde estos detalles se cazan, porque los ves fallar.

Resumen

El comando live es la única pieza de Impeccable donde el agente deja de razonar a ciegas y trabaja con feedback visual real. Su esencia es un servidor auxiliar (live.mjs) que inyecta un picker sobre tu UI, un bucle de eventos durable (live-poll.mjs) que despacha acciones del navegador, y un modelo de sesión persistente en .impeccable/live/sessions/ que sobrevive a interrupciones gracias a live-status.mjs y live-resume.mjs.

El flujo es estricto y cierra el bucle ver → editar → commit:

Con soporte para Vite, Next, Bun, HTML plano, Svelte/SvelteKit y Astro, y limitaciones claras (orden fijo, una raíz por variante, helper separado de tu app), live convierte la iteración de diseño en un diálogo en tiempo real entre tú y el agente.

Siguiente: Hooks, configuración avanzada y workflow de equipo