Expediente 358/980: la puerta que medía la caja

Una regla de CSS que el navegador descarta sin decir nada, dos tercios de cada slide recortados en un móvil, y ocho puertas en verde — porque las ocho medían la caja

Jesús Pérez
La slide se recortaba en un móvil: dos tercios de cada una, fuera de la pantalla. La regla que la escalaba estaba escrita y era legible: scale(calc(100cqw / 980)). El navegador la lee, no la entiende, y la descarta — sin excepción, sin aviso, sin una línea en la consola. Parecía correcta porque la columna del artículo mide unos 980 px. En un móvil la caja mide 358 px y el contenido seguía midiendo 980: el 63,5 % de cada slide, recortado. Y ocho puertas siguieron en verde, todas, todo el tiempo. Ninguna mintió: medían la caja, y la caja era exactamente lo que debía ser.
Expediente 358/980: la puerta que medía la caja

🕵️ Mostrar expediente completo → 📋 Protocolo de sesión →

Expediente · Depto. de Homicidios de Código

La slide se recortaba en un móvil: dos tercios de cada una, fuera de la pantalla. La regla que la escalaba estaba escrita, era legible, y llevaba días ahí: <code>transform: scale(calc(100cqw / 980))</code>. El navegador la lee, no la entiende, y la descarta — sin excepción, sin aviso, sin una línea en la consola. Parecía correcta porque la columna del artículo mide unos 980 px y ahí la slide cabía igual. Y las ocho puertas que vigilaban este trabajo siguieron en verde, todas, todo el tiempo. Ninguna mintió: medían la caja, y la caja era exactamente lo que debía ser. El contenido, dentro, seguía midiendo 980.

Caso Nº 358/980Clasificación: ANTI-PAP · MEDIR LA COSA DE AL LADOEstado: RESUELTO
Mostrar glosario
declaración inválida
Una regla CSS que el navegador no entiende no rompe la hoja: descarta esa única declaración y sigue leyendo. Es una decisión de diseño del lenguaje, y es buena — así una hoja moderna no mata a un navegador viejo. Su precio es este expediente: no hay excepción, no hay aviso, no hay línea en la consola. La regla que escribiste, sencillamente, no existe. Y todo lo que esa regla iba a cambiar se queda exactamente como estaba.
proxy de la capacidad
Una puerta mide algo y de ahí concluye otra cosa. Mide que la caja tiene el ancho correcto y concluye que la slide se ve. Mide que hay 16 nodos en el DOM y concluye que hay 16 slides legibles. Lo medido y lo afirmado son vecinos, no son lo mismo — y en el hueco entre ambos cabe un fallo entero. ADR-072 lo dice en una línea: una puerta que mide un proxy de la capacidad es una puerta que puede estar EN VERDE mientras la página está rota.
innerText / textContent
Dos formas de sacar el texto de un elemento, y no responden a la misma pregunta. textContent devuelve todo lo que hay en el árbol, esté visible o no. innerText devuelve lo que el navegador ha PINTADO. Sobre esta página servida la diferencia es exacta: 1.927 palabras están en el DOM; 19 alcanza el lector. Ninguna de las dos cifras es falsa. La trampa es elegir la que responde a la pregunta que no hiciste.
especificidad
Cuando dos reglas CSS pintan el mismo elemento, gana la más específica, y la especificidad se cuenta como un número de tres cifras: (ids, clases, elementos). Importa aquí por una propiedad que sorprende a casi todo el mundo: :not(a, b) HEREDA la especificidad de su argumento más pesado, así que cada clase que añades a una lista de exenciones sube el peso de la regla un escalón. :not(:where(a, b)) aporta cero: la lista puede crecer sin mover el peso.
proyección
Un artefacto generado desde una fuente declarada. La deriva se arregla re-ejecutando el generador, jamás editando la copia servida (ADR-057). El deck de este caso es una proyección: la fuente es el markdown de la charla, y la página del sitio se captura del renderizador que ya sabe pintarla. Nunca se analiza el markdown de Slidev — ni en Nushell ni en Rust: eso sería una segunda implementación, y una segunda implementación deriva.

Protocolo para declarar, versionar y verificar esto → ontoref.dev

El doble balance — lo que costó, y lo que dejó

Las cifras de la izquierda son el cuerpo; las de la derecha, lo que quedó en su lugar. Y la proporción que preside el expediente es la de su número: 358 y 980. La caja, correcta. El contenido, intacto dentro de ella, midiendo casi tres veces su ancho. Las dos cifras convivieron durante días, medidas, verdes y publicadas. Ninguna era falsa. Simplemente, nadie las dividió.

Lo que costó el crimen

  • La declaración que sostenía la escala de cada slide scale(calc(100cqw / 980))
  • CSS.supports() sobre esa declaración, en el navegador false
  • Lo que el CSSOM guarda al asignarla «» (cadena vacía)
  • Errores, avisos o líneas en consola que produjo 0
  • Ancho de la caja de la slide en un móvil de 390 px 358 px
  • Ancho nativo del contenido de esa misma slide 980 px
  • Proporción de cada slide recortada si la escala no se aplica 63,5 %
  • Puertas del conjunto que ya existían: A·B·C·D·E·F·H·I 8
  • …que comparasen el CONTENIDO con su caja 0
  • Slides al contar la CADENA en el HTML servido (grep -o slidev-page) 32
  • Slides al contar el ELEMENTO en el DOM (querySelectorAll) 16
  • Palabras que alcanza Ctrl+F tal como se sirve (innerText) 19
  • Palabras que hay en el DOM, siempre indexables (textContent) 1.927
  • Palabras que alcanza el lector en «Leer todo» 2.152
  • Palabras que había antes de este trabajo: el deck era un carrusel de PNG 0

Lo que el caso dejó

  • La regla mide la cosa que afirmas, no la de al lado
  • La puerta J compara el CONTENIDO con su caja, en 4 viewports
  • Lo que mide hoy la puerta J 0,994 · 0,997 · 0,998 · 0,998 (llenado del contenido)
  • La escala un ResizeObserver calcula la razón como NÚMERO en --deck-k
  • El guard :not(:where(…)) aporta cero y clava el peso en (0,1,0)
  • El conjunto, ejecutado contra la página servida 10 puertas, todas en verde
  • Lo que la puerta F encontró de paso /public/r/glosario-en.json → 404

El punto de abandono — lo que no sale en la tabla

El punto de abandono no fue no medir: fue medir, y quedarme satisfecho. Cada una de las siete trampas de esta sesión se descubrió ejecutando — el CSS pedido al navegador en vez de elegido a mano, el DOM renderizado en vez del markdown, la página servida en vez del fichero generado. Ese instinto estaba bien y por eso el trabajo salió. Pero medir no es lo mismo que medir lo que afirmas, y ahí crucé la línea ocho veces seguidas sin notarlo, porque en cada una de las ocho el número volvía correcto. No hay ninguna sensación de error al leer un número correcto. Es exactamente igual a acertar. Lo que rompió el empate no fue una idea mejor: fue abrir el deck en un móvil.

Los sospechosos — las pistas falsas

El CSS del sitio se cuela dentro del deck y aplasta la escala“«El CSS del sitio se cuela dentro del deck y aplasta la escala.» Es la primera hipótesis y la más natural: el deck vive embebido en un artículo del sitio, rodeado de los estilos del sitio, y una slide recortada parece exactamente eso. Se descarta con la puerta C, que existe precisamente para responder a esta pregunta: las reglas del sitio no alcanzan el interior del deck. La puerta estaba en verde y tenía razón. No entraba nada. Y ese es el primer aviso de lo que pasa aquí: una puerta en verde que dice la verdad no te acerca ni un paso al fallo, si está respondiendo a otra pregunta.”descartado — la puerta C tenía razón, y no servía de nada
El <style scoped> de Slidev gana por especificidad“«El <style scoped> de Slidev gana por especificidad.» Ésta era CIERTA, y no era la causa. El <style scoped> compila a .print-slide-container[data-v-HASH], que pesa (0,3,0) y empuja toda la pila de transform a la identidad. Un problema real, encontrado por el camino, arreglado atando la regla a [data-js] para llegar a (0,4,0). Y con eso, el recorte seguía intacto: puedes ganar la guerra de especificidad todo lo que quieras — una declaración inválida sigue sin existir después de ganarla.”descartado — era cierto, y no era la causa
La caja no tiene el ancho que debería — es un problema de layout“«La caja no tiene el ancho que debería.» Es lo que uno concluye viendo el contenido salirse: si algo se sale de una caja, la caja está mal. Se descarta midiéndola: 358 px en un móvil de 390. Correcto. Exacto. Lo que debía medir. La caja estuvo bien TODO el tiempo, y ése es el motivo por el que ocho puertas la miraron y no vieron nada: no había nada que ver en ella. El cadáver estaba dentro.”descartado — la caja estuvo bien todo el tiempo
«Si la regla está escrita en la hoja, el navegador la aplica»“«Si la regla está escrita en la hoja, el navegador la aplica.» CULPABLE. Y no es de nadie: es la creencia por defecto. El CSS no valida, no lanza excepciones, no escribe en la consola: descarta la declaración que no entiende y sigue leyendo — y eso es una virtud del lenguaje, porque así una hoja moderna no mata a un navegador viejo. scale() exige un número y calc() no divide una longitud entre otra, así que scale(calc(100cqw / 980)) no es CSS. Comprobado en el navegador, sin intermediarios: CSS.supports(...) devuelve false, y al asignarla el CSSOM guarda una cadena vacía. La regla estaba escrita, era legible, y no existía. Y todo lo que iba a cambiar — el contenido — se quedó exactamente como estaba, mientras todo lo que las puertas medían — la caja — seguía impecable.”CULPABLE — y es la creencia de todos

El arma — Una declaración que el navegador lee, no entiende y descarta — sin excepción, sin aviso, sin una línea en la consola

/* La regla que sostenía la escala de las 16 slides. Parece razonable.
   El navegador la lee, no la entiende, y la DESCARTA. Sin excepción, sin aviso. */

  .deck-slide .print-slide-container {
    transform: scale(calc(100cqw / 980));   /* ← inválido: scale() exige un NÚMERO,
                                                y calc() no divide longitud entre longitud */
  }

/* Verificado en esta sesión, en el navegador, sin intermediarios:

     CSS.supports('transform', 'scale(calc(100cqw / 980))')  →  false
     div.style.transform = 'scale(calc(100cqw / 980))'
     div.style.transform                                     →  ""      (no guardó nada)

   Parecía correcta porque la columna del artículo mide ~980 px: sin escala, la slide
   cabía. En un móvil de 390 px la caja mide 358 px — y el contenido seguía midiendo 980.

     1 − 358/980  =  63,5 % de cada slide, recortado.

   Y las ocho puertas que ya existían siguieron en verde, porque las ocho medían LA CAJA:
   la caja era 358 px, que es exactamente lo que la caja debía medir. Todas decían la
   verdad. Ninguna estaba mirando el contenido. */

Y aquí está la parte que convierte esto en un expediente y no en un descuido. Este trabajo estaba vigilado por ocho puertas —contención, fidelidad, sin fuga de entrada, glosario, slides, fuentes, navegación, zoom— y las ocho estuvieron en verde todo el tiempo. Ninguna mintió. La puerta que contaba slides contó 16 nodos y había 16. La que medía la caja midió 358 px y la caja medía 358 px. La que comprobaba que no entraran estilos ajenos tenía razón: no entraba ninguno. Ocho verdades, y ni una sola de ellas era la afirmación que yo creía estar comprobando — que un lector puede leer la slide. Ninguna puerta comparaba el contenido con su caja. La única división que hacía falta —980 dentro de 358— no la hacía nadie.

El giro — Calcular la razón como número, y añadir la única puerta que mira el contenido

// 1. La razón que el CSS no sabe expresar — ancho de la caja / ancho nativo — la
//    calcula quien SÍ puede medirla, y la deja escrita como un número puro.

  var W = 980;
  var ro = new ResizeObserver(function (entries) {
    for (var e of entries) {
      var k = e.contentRect.width / W;          // ← un NÚMERO, no una longitud
      e.target.style.setProperty('--deck-k', k);
    }
  });

  .deck-slide .print-slide-container[data-js] {
    transform: scale(var(--deck-k, 1));         // ← (0,4,0): por encima del
  }                                             //   [data-v-HASH] de Slidev, que es (0,3,0)

// 2. Y la puerta que no existía — la única que mira el CONTENIDO y no la caja.
//    No repara este fallo: repara la clase entera de fallos.

  const m = await p.evaluate(() => {
    const s = document.querySelector('.deck-slide[data-active]')
    const c = s.querySelector('.print-slide-container')
    const sb = s.getBoundingClientRect(), cb = c.getBoundingClientRect()
    return { boxW: sb.width, cW: cb.width }     // ← la caja Y el contenido
  })
  const fill = m.cW / m.boxW                    // ← y la razón entre ambos
  if (!(fill > 0.97 && fill < 1.03))
    fail('J_escala', `contenido al ${(fill*100).toFixed(0)} % de su caja (recortado)`)

// Sobre 4 viewports — 1920, 1440, 820 y 390 px. La caja se sigue midiendo.
// Ya no se CONCLUYE nada de ella.

La caja se sigue midiendo. Eso importa decirlo, porque la lección de este expediente se cuenta mal si se cuenta como «medir la caja está mal». Medir la caja está bien. Lo que estaba mal era concluir de la caja algo que la caja no dice. Las ocho puertas siguen ahí, verdes, útiles, y ninguna se ha tocado: se les ha añadido la novena, que hace la única división que ninguna hacía. Y queda una deuda, dicha aquí y no escondida en el último párrafo: las diez puertas todavía no están en just check. Están en verde y están fuera de la cadena, que es exactamente la patología del expediente 0/318 — un validador impecable que nadie ejecuta. Este caso no la ha cerrado. La ha nombrado.

El veredicto

La serie pregunta: ¿con ontoref no hubiera sido así? Y este caso responde con una incomodidad muy concreta, porque el mecanismo que lo habría cazado ya estaba escrito, y lleva número. ADR-072 dice, sin ambigüedad, que una puerta que mide un proxy de la capacidad es una puerta que puede estar en verde mientras la página está rota; el howto de esta misma serie lo repite en mayúsculas —«verifica la TARJETA renderizada, no un número en el HTML»—; y el expediente 3/0 es literalmente el caso de una comprobación que solo verificaba lo que EXISTÍA y nunca lo que DEBÍA existir. Todo eso estaba en el repositorio antes de que este deck se proyectara. Y aun así, ocho puertas nuevas nacieron midiendo la caja. Porque la regla, escrita, no basta: hay que preguntarle a cada puerta qué afirma, y luego mirar si eso es lo que mide — y no hay nada más incómodo de mirar que una puerta propia, en verde, que dice la verdad. El fallo, en las siete trampas de esta sesión, fue siempre el mismo: la caja en vez del contenido, los nodos del DOM en vez de lo renderizado, innerText en vez de textContent, la cadena en el HTML en vez del elemento en el árbol. Cuatro variantes de un solo error, y ninguna de las cuatro produjo jamás un mensaje de error. Medir la cosa de al lado no falla. Ése es todo el problema.

Una puerta que mide un proxy de la capacidadLa puerta declara QUÉ afirma y mide exactamente eso. «Las slides se ven» no se demuestra midiendo la caja: se demuestra comparando el contenido con la caja. La puerta J lo hace sobre cuatro viewports, y hoy mide 0,994–0,998. Es una sola división más que las otras ocho no hacían — y es la diferencia entre una puerta y un adorno.
Un lenguaje que falla en silencio por diseñoEl CSS descarta lo que no entiende y no lo dirá nunca. Eso no se repara desde aquí: se CONTIENE midiendo el efecto en vez de confiar en la causa. La regla estaba escrita, era legible, y no existía. Lo único que podía delatarla era mirar lo que la regla debía haber cambiado. Contener no es curar, y el expediente lo dice en vez de disimularlo.
Un número que sale de contar la cadena en vez del elementoEl conteo de slides dio 32 sobre 16 reales, porque grep -o slidev-page cuenta la CADENA — y esa clase aparece dos veces por slide. querySelectorAll('.slidev-page').length cuenta el ELEMENTO, y da 16. La misma trampa un nivel más abajo: un número correcto sobre la pregunta equivocada es indistinguible de un número correcto.
Elegir el método que responde a otra preguntatextContent dice qué hay en el árbol; innerText dice qué se ha pintado. Sobre esta página: 1.927 y 19. Ambas ciertas, y una sola sirve para afirmar «el lector alcanza el texto». La puerta E imprime LAS DOS, siempre, en vez de elegir la que le conviene: un dato que se enseña no se puede usar para engañarse.
Una lista de exenciones que sube el peso de la regla al crecer:not(a, b) hereda la especificidad de su argumento, así que el contrato de frontera se hacía más pesado con cada clase que se le añadía — y a las dos exenciones ya pisaba la escala de las slides. :not(:where(a, b)) aporta cero. La lista puede crecer sin mover el peso: el mecanismo deja de caducar por usarlo.
Un conjunto de puertas que nadie ejecutaLas diez puertas existen y hoy están en verde sobre la página servida. Todavía NO están en just check. Un generador fuera de la cadena es un generador que no corre, y una puerta fuera de la cadena es una puerta que no vigila: es la misma patología del expediente 0/318, y este expediente la declara abierta en lugar de darla por hecha.

La jurisprudencia — qué exige hoy la lección

  • Una puerta declara qué afirma y mide exactamente eso — nunca la caja de al ladoadr:072/a-check-measures-the-capability-not-a-proxy
  • Un conjunto de puertas que nadie ejecuta no es cobertura: la puerta entra en la cadenaadr:071/the-gate-is-in-the-chain

Queda una cosa por decir, y ordena a este expediente junto a los otros de la serie. El 1/1852 verificó el fichero y no la página. El 3/0 comprobó lo que existía y nunca lo que debía existir. El 69/58 miró el árbol que nadie sirve. El 0/318 escribió un validador impecable y no lo ejecutó. Los cuatro tienen el mismo esqueleto que éste: un artefacto afirma algo sobre sí mismo, y lo que se comprueba es lo de al lado. Cambia el vecino —el fichero en vez de la página, el árbol en vez del servido, la caja en vez del contenido— y no cambia nada más. Por eso este caso no añade una puerta y se calla: añade la pregunta que hay que hacerle a cualquier puerta antes de creerle. ¿Qué afirmas? ¿Y es eso lo que mides? Si las dos respuestas no son la misma frase, la puerta es verde y es decorativa, y el día que la página se rompa te lo dirá un lector con un móvil en la mano.

Glosario

¿Te ha resultado útil? Valóralo
¿Tienes algo que aportar? Cuéntame qué opinas, qué sugieres, o si seguimos explorando este tema.
· lecturas

Usamos cookies para que este sitio funcione, entender el uso del servicio y apoyar acciones de marketing. Política de cookies para más información.