Del uso a la experiencia
Un informe nombra un síntoma. El trabajo es la experiencia detrás de él. «El cliente desaparece cuando la conversación pasa al asunto» es un error. «Una conversación reúne de qué se trata: el cliente se queda cuando se añade su asunto» es lo que hay que construir, verificar y filmar. Escribe esa frase primero. Arregla la estructura que permitió el síntoma, luego demuestra la frase usando el producto.
El bucle
- Recopila el uso. Lee el informe, luego las trazas de uso real: las conversaciones, diarios, registros, presencia, los archivos que tocó la persona. Contienen expectativas que nadie escribió («ils en sont où ?» espera los puntos mismos, no un resumen).
- Escribe la experiencia. Una frase por informe, en presente, desde el lado de la persona, sin palabras de implementación. Mantén la lista. Se convierte en la narración de la película y en los mensajes de las afirmaciones.
- Encuentra la razón estructural. Pregunta por qué el producto podía hacer esto en absoluto. Una causa suele explicar varios informes (identidad tomada de un nombre para mostrar; dos recuentos que comparten una palabra; un renderizador que elimina una clase al insertar). Arregla la causa, no la pantalla que la mostró.
- Úsalo tú mismo. Conduce la aplicación real como cada persona involucrada, en ancho de escritorio y teléfono, a través del espacio de nombres del navegador (abajo). Mira las capturas de pantalla. Cuenta lo que dice la página, no lo que pretende el código.
- Consérvalo. Convierte los pasos manuales en un escenario: las acciones se convierten en eventos, las comprobaciones en afirmaciones, la frase de experiencia se convierte en la narración de la escena. Pon los turnos del modelo en cinta para que la película se reproduzca en segundos, sin coste.
- Informa la experiencia, no el parche. En el resumen, lidera con lo que la persona encontrará ahora, luego la causa, luego la película y cómo reproducirla.
Gusto de la casa (lo que Othman ha pedido, repetidamente)
- Menos texto. El estado se dibuja: tono, peso, ubicación, un anillo, una marca. Una frase que explica una pantalla significa que la pantalla está mal.
- Una palabra por idea. Mantén un glosario y respétalo: un «critère» (juzgado contra el procedimiento) nunca es un «point» (lo que una persona decide). Si dos recuentos comparten una palabra, alguien los sumará.
- Las cosas se acumulan. Las etiquetas se suman (tipo, luego cliente, luego asunto). La actual se rellena y las demás permanecen como contornos. Nunca reemplaces el contexto que la persona ya vio.
- Números dibujados antes que texto. Un historial se abre en gráficos, una serie cada uno, valores etiquetados. El texto se sitúa debajo, una línea por evento, y se abre bajo demanda.
- Cada referencia lleva a su lugar. Un número de párrafo desplaza al párrafo y lo ilumina. Una cita encuentra su pasaje. La barra de desplazamiento marca dónde están las cosas, en sus colores.
- En vivo por defecto. Sin recargas completas. Un cambio hecho por una persona se muestra en todas las páginas abiertas a la vez. Los toques repetidos en una página lenta no deben repetir el efecto: los gestos son idempotentes.
- Controles donde está la cosa, y solo para quienes puedan usarlos. Las personas de solo lectura ven el estado sin el control.
- Saber quién está ahí. La identidad y el rol llegan a cada pantalla y a cada agente. No le digas a un socio que «un socio debe confirmar».
- Bordes honestos. Di claramente lo que el producto todavía no hace. Nunca apuntes a una pantalla que no pueda hacerlo.
- El ancho de teléfono es un lector de primera clase: márgenes de 16px, sin desplazamiento horizontal (
minmax(0,1fr), no1fr), y la cuenta siempre accesible. - Maquetas y narración en el idioma del producto (francés para Knowledger).
Prueba manual con el espacio de nombres del navegador
El navegador nativo de Beam Lisp (optional/browser-port en el árbol beam-lisp) ejecuta Chromium con interfaz gráfica en un Sway sin cabeza privado, con entrada confiable y una transmisión de pantalla continua. Es el mismo objeto que maneja un escenario, por lo que una sesión manual crece hasta convertirse en una prueba sin ser reescrita. Usa agent-browser para una mirada rápida. Usa el espacio de nombres del navegador para todo lo que valga la pena conservar.
Una vez por máquina
cd ~/projects/beam-lisp/optional/browser-port
nix develop --command cargo build --release --locked --features native-browser
nix build .#runtime-wayland --out-link ~/.local/share/bl-browser/runtime-wayland # chromium, sway, wayvnc
command -v ffmpeg || nix profile add nixpkgs#ffmpeg
Ejecuta cualquier cosa que lo use con bl run -p ~/projects/beam-lisp/optional/browser-port/bl …
(o bl test -p …).
Una sonda: un archivo, una persona, entrada real
templates/probe.bl es el punto de partida. Su núcleo:
(ns probe (:require [browser.page :as page]))
(def rt (str (System/get_env "HOME") "/.local/share/bl-browser/runtime-wayland/bin/"))
(def p (page/open! {:executable (str (System/get_env "HOME") "/projects/beam-lisp/optional/browser-port/target/release/bl-browser-port")
:width 1440 :height 860
:launch {"profile" "/tmp/probe/profile" "runtime" "/tmp/probe/runtime"
"chromium" (str rt "chromium") "compositor" (str rt "sway") "vnc" (str rt "wayvnc")
"width" 1440 "height" 960}}))
(try
;; quién mira: la identidad que el proxy añadiría (aquí cabeceras Tailscale); también llega al socket en vivo
(page/cdp! p "Network.enable" {})
(page/cdp! p "Network.setExtraHTTPHeaders" {"headers" {"tailscale-user-login" "[email protected]" "tailscale-user-name" "timinou"}})
(page/record! p "/tmp/probe/take.mp4" {:fps 24}) ; opcional: toda la sesión como película
(page/navigate! p "http://127.0.0.1:24025/a/sahel-…")
(page/wait! p "document.documentElement.dataset.live==='1'") ; un predicado JS, límite de 30 s por defecto
(page/click! p ".rv-rail a.para[data-goto]") ; puntero confiable: mueve, presiona, suelta
(println (page/evaluate! p "scrollY")) ; pregunta a la página lo que una persona vería
(page/screenshot! p "/tmp/probe/after.png") ; luego MÍRALO
(page/stop-recording! p)
(finally (page/close! p)))
Los verbos son navigate! wait! click! hover! fill! key! drag! evaluate! screenshot! record! stop-recording! show-cursor! cdp!. cdp! accede a cualquier otra cosa que Chromium ofrezca: viewport, red, cookies, emulación (Emulation.setDeviceMetricsOverride para un teléfono).
Hábitos que hacen que una sonda valga la pena conservarla
- Pregunta a la página, no al código. Cada comprobación es una expresión JS que devuelve
truecuando una persona vería la cosa: recuentos de filas, el texto de una etiqueta,scrollY, una clase en el elemento bajo el puntero. Esas expresiones se convierten en afirmaciones sin cambios. - Selecciona por significado. Dale a la aplicación atributos de datos estables donde las pruebas los necesiten (
button.cell[data-person='Karim Saadi'][data-team='Associés']), nunca «el quinto botón». - Sé cada persona. Abre una página por persona con su identidad. Dos páginas una al lado de la otra es como se demuestra lo «en vivo»: una actúa, la otra cambia sin recarga.
- Comprueba la primera pintura y la página parcheada. Las aplicaciones en vivo renderizan HTML una vez, luego parchean a partir de datos. Los errores se esconden en la diferencia (una clase eliminada al insertar, una lista que comienza con texto leído como nombre de etiqueta). Alcanza cada pantalla una vez por URL y otra vez navegando hacia ella.
- Espera a que la página esté en vivo, no solo conectada. Un cliente en vivo puede reemplazar la página renderizada por el servidor con el primer renderizado de su socket. El texto escrito entre medias se pierde, tanto para una prueba como para una persona que escribe rápidamente después de la carga. La aplicación debe conservar lo escrito durante ese montaje y luego decir que está en vivo (
data-mounted). Las pruebas esperan esa bandera. - Mira el ancho de teléfono. Comprueba
document.documentElement.scrollWidth === innerWidtha 390px, luego mira la captura de pantalla. - Nunca toques datos reales, y demuéstralo. Apunta la aplicación a una copia reflink de sus datos (
cp -a --reflink=auto) y a un puerto de repuesto. Luego demuestra el aislamiento antes de confiar en él: renombra una cosa en la copia (un canario: un equipo «AssociésX»), actúa sobre ella, y comprueba que la copia cambió y la marca de tiempo del archivo real no. Haz esto una vez por cada tipo de proceso que escribe (manejadores de página, sockets en vivo, trabajos en segundo plano). En Beam Lisp,System/put_envdesde una prueba alcanza los manejadores de página pero no los sockets en vivo: un socket que no lee una carpeta de datos configurada recurre al valor por defecto, que son los datos reales. La aplicación debe fijar su configuración en toda la VM cuando su servidor arranca (persistent_term) y leerla desde allí. Si debes tocar datos en vivo, haz una instantánea del archivo, actúa, compara para confirmar solo tu cambio, restaura.
De la sonda a la película
Un scenario.demo/defdemo es una prueba ordinaria (bl test). Los pasos se corresponden uno a uno:
| Sonda | Escenario |
|---|---|
(page/navigate! p url) | (s/does :othman [:navigate url]) |
(page/wait! p js) | (s/does :othman [:wait js]) |
(page/click! p sel) / hover! / fill! / key! | [:click sel] [:hover sel] [:fill sel text] [:key "Enter"] |
| una pausa para el ojo | [:pause ms] |
(page/evaluate! p js) que debe ser verdadero | (s/sees :othman (fn [p _] (= true (page/evaluate! p js))) "la experiencia") |
| una persona | un actor en browser.film/owned-worlds, envuelto para establecer su identidad al abrir |
| un párrafo de narración | (demo/scene :id {:title … :say … :capture :video} events…) |
templates/feature-demo.bl es un archivo completo: una copia aislada de los datos de la aplicación, la aplicación iniciada en la VM de prueba en un puerto de repuesto, mundos grabados con identidades, pistas silenciosas cuando no existe una clave de voz, y una demostración con tres escenas. Ejemplo funcional: knowledger-interface-review/apps/practice/test/features-demo.bl (cinco películas: revisión, historial, piezas, un equipo cambiado en vivo entre dos personas, una conversación grabada).
Escribe las escenas como la experiencia, no la corrección: «Cada punto lleva a su lugar», nunca «arregla el manejador §-link». La narración es la documentación.
Turnos del modelo en cinta
Cualquier cosa que haga un agente debe pasar por la propia puerta de herramientas de la aplicación, para que solo las elecciones del modelo sean del modelo. Una cinta conserva, para una ejecución del modelo, las llamadas a la puerta que hizo en orden y su respuesta final, identificadas por un hash del modelo, el prompt del sistema y el prompt (con relojes, números tipo época e ids hexadecimales enmascarados).
record: reproduce lo que está en cinta, ejecuta el modelo para lo que no está, consérvalo.replay(el valor por defecto): una ejecución no en cinta es un turno de error. Una reproducción nunca recurre al modelo en silencio.- Replay hace las mismas llamadas a través de la misma puerta, sincronizadas, para que el producto haga el trabajo de nuevo y la pantalla lo muestre, en segundos y gratis.
Fija todo lo que llega al prompt (el nombre de la empresa, la fecha si la hay) en los datos de prueba, o la clave derivará. Confirma las cintas. Implementación de referencia: apps/practice/src/practice/cassette.bl y sus dos ganchos en conversation.bl (el turno y la puerta).
Ejecución y renderizado
bl test -p ~/projects/beam-lisp/optional/browser-port/bl app/test/features-demo.bl # replay, verifica, captura
DEMO_RECORD=1 bl test -p … # pon los turnos del modelo que faltan en cinta (necesita credenciales del modelo)
BL_DEMO_EXPORT=1 bl test -p … # también renderiza MP4 + subtítulos + capítulos por película
Cada película limpia su última toma en su fixture, porque una grabación nunca sobrescribe una. Mantén cada narración dentro de dos líneas de subtítulo (aproximadamente 130 caracteres), o el renderizado la rechazará. Una máquina cargada puede privar de recursos al codificador de la transmisión de pantalla: comprueba frames/recordings.json, donde encodedFrames debería ser aproximadamente segundos × fps, y vuelve a ejecutar cuando una toma sea corta.
El renderizado (scenario.film/export!) necesita Python 3 y FFmpeg con libass. La narración se habla cuando DEEPGRAM_API_KEY está configurada. De lo contrario, cada escena recibe una pista silenciosa tan larga como su subtítulo tarda en leerse. Nunca asumas que existe una clave de voz. Navega por las películas con (scenario.demo/serve! dir {:port 4163}) en loopback, o publícalo con work serve.
Antes de que digas que está terminado
- Cada elemento informado tiene su frase de experiencia, su causa y una afirmación que lo demuestra en un navegador real, como cada persona involucrada.
- Se ha mirado el ancho de escritorio y teléfono, en capturas de pantalla, no solo afirmado.
- Se han comprobado tanto la primera pintura como la página parcheada para cada pantalla tocada.
- El comportamiento en vivo se ha demostrado con dos personas, donde sea relevante.
- No se han modificado datos reales mediante las pruebas. Todo lo tocado se restaura, mostrando la diferencia.
- Las películas se reproducen desde cinta sin red. Las cintas y los escenarios están confirmados, los renderizados no.
- El resumen lidera con lo que la persona encontrará ahora.