Live browser iteration: el comando live
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:
- El agente arranca un servidor auxiliar que inyecta una capa de edición (un picker) sobre tu página.
- Tú seleccionas elementos en el navegador y pides variantes, o editas a mano.
- El agente genera alternativas, las intercambia en caliente vía HMR y espera tu elección.
- Cuando aceptas, el cambio se carboniza y se reescribe limpio en el código fuente.
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.
| Script | Rol |
|---|---|
live.mjs | Arranca el servidor auxiliar e inyecta el picker en los HTML configurados |
detect-csp.mjs | Identifica la forma de la CSP y los puntos a parchear durante el bootstrap |
live-poll.mjs | Bucle de eventos: bloquea esperando acciones del navegador |
live-wrap.mjs | Envuelve el elemento seleccionado para alojar variantes |
live-insert.mjs | Modo insertar: andamia contenido nuevo en un punto de anclaje |
live-accept.mjs | Persiste la variante aceptada y gestiona la limpieza de la carbonización (carbonize) |
live-complete.mjs | Acuse manual de cierre de sesión tras la carbonización |
live-status.mjs | Reporta el estado de sesiones pendientes |
live-resume.mjs | Recupera una sesión interrumpida desde el journal |
live-server.mjs stop | Detiene 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:
serverPort: el puerto del servidor auxiliar (sirve/live.jsy el canal de eventos SSE).serverToken: token de la sesión, para autenticar las llamadas.pageFiles: la lista de archivos HTML instrumentados con el picker.
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:
generate: pediste variantes para un elemento.steer: diste una indicación a nivel de página, sin seleccionar elemento.accept/discard: elegiste o descartaste una variante.prefetch: pre-carga anticipada.manual_edit_apply: pediste aplicar un lote de ediciones manuales.exit: fin de la sesión.
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:
- Localizar el bloque de carbonización por sus marcadores.
- Mover el CSS inline a la hoja de estilos del proyecto y reapuntar los selectores a clases semánticas.
- Hornear los valores de parámetros (eliminar ramas de
steps/toggleno elegidas; sustituir o resetear losrange). - Desenvolver el contenido aceptado (borrar el envoltorio
data-impeccable-variant). - 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:
batch.entries: las entradas de edición del lote.evidencePath: la ruta opcional a la evidencia capturada (la prueba visual de lo que editaste).repair: bandera de reparación.deadlineMs: el plazo (en milisegundos) para responder.
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.
- Vite / Next.js / Bun / HTML plano: ruta estándar HTML/JSX/TSX. Las variantes se escriben como envoltorios
<div data-impeccable-variant>directamente. - Svelte / SvelteKit: ruta especial
previewMode: "svelte-component". Las variantes se escriben como componentes.sveltereales en uncomponentDirtemporal, y los parámetros se declaran encomponentDir/params.json(no como atributos en el envoltorio, que romperían el parser de Svelte). Al aceptar, los componentes se inlinean en el código y la sesión temporal se borra. - Astro: patrones glob multipágina en la configuración; el parche de CSP usa la forma
append-arraysen el archivo de config.
Limitaciones que conviene tener presentes
live es potente, pero tiene fronteras claras. Conocerlas evita frustración:
- Orden fijo: no se pueden reordenar las fases bootstrap → navegar → long-poll.
- Helper ≠ servidor de app: el
serverPortsolo sirve/live.jsy SSE; tu app vive en su propio puerto. - Modo replace por defecto: el modo insertar exige
freeformPromptno vacío o anotaciones. - Una raíz por variante: cada
data-impeccable-variantdebe tener un único elemento de nivel superior. - CSS scoped por el envoltorio: el
@scopese ancla en el<div>de la variante, no en el reemplazo interno. - Reset de parámetros al cambiar: si ajustas la variante 1 y saltas a la 2, la 2 arranca en sus valores por defecto declarados. Es una limitación conocida.
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:
- Generar variantes (
generate→live-wrap.mjs): tres alternativas hot-swappables que cicleas en el navegador. - Editar a mano (
manual_edit_apply): un buffer de ediciones sobre el DOM que captura evidencia y hace commit de vuelta al código, recuperable hasta ellive-complete.mjs. - Dirigir (
steer): indicaciones a nivel de página sin seleccionar elemento. - Carbonizar y limpiar al aceptar: reescribir el cambio temporal en forma permanente y semántica.
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