You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/CONTINUE-theming.md

151 KiB

CONTINUE — eje Theming «theme-reach» — CERRADO 2026-08-25

EMPIEZA AQUÍ

EL EJE ESTÁ CERRADO (gate de F3 rediseñado y en verde el 2026-08-25; sección §«EL CIERRE, EJECUTADO» abajo). Lo que vigila el estado desde hoy no es este documento sino los guards: theming-reach-floor.test.ts (newDebt=0 · STALE=0 · reach ≥ 73 % · ≥65 al 100 %), component-audit R-5.1/5.2/5.3 en error, y el centinela R-5.4. La deuda restante vive NOMBRADA clave a clave en scripts/theming-census-debt.ts (1154 entradas que sólo pueden menguar). Trabajo nuevo de theming se abre como entrada nueva de §13/next-features, no heredando de aquí. Las DOS firmas que el cierre dejó pendientes están EJECUTADAS: la de los 318 privados no-derivados (§«FIRMA EJECUTADA — los 318» abajo) y la del idioma var(--{c}-x, fallback) sin contrato (§«FIRMA EJECUTADA — la ley del espacio cerrado»). Heredada de la segunda queda la re-clasificación de los 117 nombres --palabras-* que el censo puntúa public siendo canal de VALOR, más DOS deudas nuevas de eje propio que ella misma nombró: el codemod de los 14 canales públicos a --_{c}-* — EJECUTADO 2026-08-26 (14 de 14: 13 en el codemod + el 14.º por firma propia, ver §«FIRMA EJECUTADA — el indicador de navigation-menu») — y el cableado EidosConfig.motion → CSS (las dos, con su lista, en next-features.md). Lo que sigue pendiente en este carril: el cableado de motion · la re-clasificación de palabras · la timeline §5 pre-B′.

El eje ya no tiene cola. Los 16 componentes que nunca habían pasado están cerrados o adjudicados, la deuda de artefactos está saldada, y §12.9 y la firma B′ quedaron FIRMADAS Y EJECUTADAS — las dos decisiones más caras pendientes. Global 73 % (68 % antes de la clase structural, 69 % antes de bridge/channel), 65 componentes al 100 %.

HANDOFF 2026-08-25 (noche) — POR AQUÍ MAÑANA

Las CINCO firmas del eje están EJECUTADAS y pusheadas (2daa683fa file-upload · df3a4d054 form/compuesto · 2ce43db56 tags-input aria · 1f8ce293b avatar · 466b24f34 chip/suelo §55). Después cayeron los expedientes de §13: d5a27b87e (árbol ARIA de tags-input, cierra F-1) · 3bf7930ba (el velo no muere por un atajo + pin de chronos, §56) · 318aac207 (la talla de form LLEGA, doctrina anchura-vs-densidad).

Lo PRIMERO mañana: dos firmas de INSTRUMENTO, en este orden

  1. «La mitad gratis» — ✅ FIRMADA Y EJECUTADA 2026-08-25 (047464389; ver §«FIRMA EJECUTADA — la mitad gratis» justo abajo). Expediente original: scratchpad de la sesión 6314e22b…/scratchpad/bprime/exped/va/informe.md (1.019 líneas).
  2. La GRANDE: cuatro cegueras + TRES BARRIDOS escalonados. — ✅ FIRMADA Y EJECUTADA 2026-08-25 (6392d6003 S1 · b4dd5a906 S2 · b9cef9a43 S3; ver §«FIRMA EJECUTADA — la GRANDE» abajo). Se ejecutó exactamente con el plan de VA: tres barridos escalonados, sin fusionar S2 y S3, y la atribución quedó limpia.

FIRMA EJECUTADA (2026-08-26) — el codemod de los 14 canales (13 de 14)

Qué se ejecutó: los nombres públicos-con-forma que soma o el envoltorio escriben POR INSTANCIA pasan a privados --_{c}-* — el destino que la firma de los 318 ya había firmado (clase channel) y que el registro PENDING_PRIVATE_RENAME sólo contaba. Trece renombrados, sus trece entradas del registro RETIRADAS con acta; el registro queda en DOS (no en una): la de motion, que era la prevista, y una PARADA.

El método, y es la mitad del resultado: barrido git ls-files de cada nombre por el árbol trackeado entero (94 ocurrencias en 36 ficheros), y flip ATÓMICO por nombre — escritor y TODOS los lectores en la misma edición. Nada de grepear los propios patrones: el barrido destapó lectores que ninguna lista traía (el comentario del morfo de knob, las dos páginas de demo, nueve menciones en PLAN-background.md).

✅ RESUELTO — --navigation-menu-indicator-h, con (a) FIRMADA Y EJECUTADA (2026-08-26, tarde). Estuvo PARADO porque no era un canal suelto: el provider escribe CUATRO hermanas en un mismo literal, el README de soma las enseñaba las cuatro bajo ## CSS Variables como forma controlada por el consumidor (con dos ejemplos leyendo -h), y -w era clave DECLARADA del contrato — la única razón por la que el guard veía a -h sola. Ver §«FIRMA EJECUTADA — el indicador de navigation-menu» abajo. El registro queda en UNA (motion).

⚠ LA TRAMPA DEL CENSO NO MORDIÓ, y la predicción estaba MAL por un orden de magnitud. Se esperaba que ~15 knobs pasaran de public a channel y que el suelo pudiera rozar el redondeo. Medido: UNA sola clave se reclasifica —color-swatch.background— porque el censo cuenta knobs de APARIENCIA, y las otras doce viven en declaraciones de GEOMETRÍA (translate, rotate, inline-size, grid-template-columns, animation-duration) o en declaraciones que conservan un público hermano y siguen puntuando public (el conic-gradient de knob lee --knob-track-bg; el panel de menubar lee --menubar-panel-min-width como fallback). Números: public 3074 → 3073, channel 36 → 37, denominador 4228 → 4227, reach 73 %, atHundred 65, ledger 1154 · 0 NEW · 0 STALE. El suelo NO se toca — no hubo nada que ajustar ni que fingir.

Neutralidad, medida en dos planos. Estática: sonda estándar antes/después sobre los 7 componentes, 0 diffs reales; el único diff (emoji-picker, un background-image de velo en el trigger, bloque open) se reprodujo 2/2 sobre código idéntico — ruido del instrumento. ⚠ color-swatch no tiene ruta de demo (404) y la sonda devuelve 0 nodos: su «0 diffs» es un guard que no inspecciona nada y NO cuenta como prueba; se midió donde de verdad monta. Dinámica (donde estos canales viven y la sonda no llega), 7/7 por CDP: background puntero →_x −0.7/0.8, _y −0.6/0.7, _px 15 %/90 %, _py 20 %/85 %, translate −28px −24px → 32px 28px (= x × depth 40px, exacto) y el foco del spotlight at 15 % 20 % → at 90 % 85 %; el quinto canal (--_background-progress) es la rama @supports not(view()) que Chrome nunca corre, verificada forzando el gate: 0.379 → 1 al scrollear · knob accionado por teclado, _angle −27deg → 0deg, _progress 0.4 → 0.5, el indicador rota igual y la parada del arco 108deg → 135deg (= progress × sweep) · color-swatch tres chips, #7c3aed/#06b6d4/#f59e0b → rgb(124,58,237) / rgb(6,182,212) / rgb(245,158,11) · emoji-picker privado 9 → 9 tracks, y moverlo a 6 y a 12 da 6 y 12 tracks, mientras fijar el nombre VIEJO a 3 no mueve nada (control negativo) · menubar panel anclado, _anchorWidth 886px = barra 886 = panel 886 · navigation-menu (el PARADO) sin regresión, -h sigue pública en 36px = altura del trigger activo · text-gradient inline 8s → animation-duration: 8s en el hijo [data-text-gradient-fill]. En los siete, el nombre VIEJO computa vacío.

Guard mutado por sus dos caras: reintroducir una entrada retirada → STALE nombrándola; devolver un nombre público viejo a una receta → missing nombrándolo. Suites de los tocados 18/18, component:audit 162 PASS / 4 NEEDS-WORK idéntico, docs:check 0/0, --names 0 desviadas, npm run check 0 errores atribuibles (verificado POR FICHERO).

Docs: se ANOTARON, no se reescribieron — fichas medidas, README de color-swatch, PLAN-background.md (una anotación fechada cubre sus nueve) y STUMBLES.md §7. El nombre nuevo sí se volcó donde el texto describe código VIVO (READMEs de componente y las dos demos). Queda anotada la fila GENERADA audit/theming/background.md:72, que la regenerará el --report pendiente.

Hallazgo lateral, ajeno: el control columns de la demo de emoji-picker es un <Slider> DESNUDO y slider.svelte renderiza sólo su provider — un div[data-slider] vacío, sin raíl ni pulgar. No se puede accionar.

FIRMA EJECUTADA (2026-08-26) — el indicador de navigation-menu

Qué se firmó: la opción (a) del PARADO. Las CUATRO medidas del trigger activo pasan a privadas — --_navigation-menu-indicator-{x,y,w,h}, flip ATÓMICO escritor + todos los lectores, con barrido git ls-files por nombre —, indicator-w se RETIRA del contrato (44 → 43 filas) y la sección ## CSS Variables del README de soma se reescribe al camino de la casa: el consumidor estiliza la PARTE ([data-navigation-menu-indicator] y su data-state) con los tres pomos de forma, no las medidas.

Lo que se RECHAZÓ, y por qué importa: (b) declarar -x/-y/-h para casar con el README bendeciría como superficie de tema cuatro medidas por instancia — justo lo que la clase channel de la firma de los 318 existe para negar. Y se rechazó una especie nueva, output (tercera clase entre público y privado): abre excepciones sobre una clase barata con frontera de PROSA, patrón ya rechazado dos veces.

-w era un token-que-miente, MEDIDO (CDP, transiciones CONGELADAS — en esta parte inline-size va transicionada y una lectura inmediata devuelve el valor VIEJO; el probe informaba «no se movió» de TODO hasta congelarlas): con el subrayado pintado, escribirlo en la raíz del componente —lo que hace el centinela— deja inline-size en 107px, y en :root igual; sólo una regla !important de autor llega (1234px), porque soma escribe INLINE en el elemento. La única ventana donde la declaración asomaba es el indicador montado SIN rect todavía (isOpen true, triggerRect aún null; o forceMount): ahí inline-size lee el 0px que daba esta clave y un write en el host lo mueve a 1234px. Es decir, llegaba sólo donde no pintaba nada. ⚠ El indicador está montado CONDICIONALMENTE ({#if state.isOpen || forceMount}): con la barra cerrada hay 0 nodos en la página.

Números: public 3073 → 3072, channel 37 → 38, denominador 4227 → 4226, reach 73 % y atHundred 65 intactos, --names 4567 → 4566 (0 desviadas), ledger 1154 · 0 new · 0 stale. Centinela navigation-menu 43/44 → 42/43, misma única adjudicación (disabled-trigger-fg): el total baja porque la clave sale del contrato, no porque nada deje de llegar — el fichero de excepciones no se toca. component:audit 162 PASS / 4 NEEDS-WORK idéntico.

Dinámica, que es la que vale aquí (la sonda estándar es ciega a estos canales): Products w=107px h=36px = rect del trigger, Solutions w=108.5625px h=36px, inline-size las sigue (107 → 108.562) y left 0px → 111px — el subrayado DESLIZA — con translateY(36px) estable. Control negativo: los nombres viejos computan VACÍO en el indicador, y fijarlos en la raíz a 777px/333px no mueve nada. Control positivo: los tres pomos del contrato siguen llegando (block-size 2 → 9px, background → rgb(1,2,3)).

Guard: PENDING_PRIVATE_RENAME queda en UNA entrada (motion), con acta en el propio registro. Mutado por sus dos caras: reintroducir la entrada retirada → STALE nombrándola; devolver --navigation-menu-indicator-w a la receta → missing nombrándolo.

⚠ Trampa del barrido: el grep del nombre LITERAL es CIEGO a la forma con llaves --…-indicator-{x,y,w,h}, que escondía tres menciones más (el comentario de navigation-menu.svelte de eidos y dos filas de la demo). Hay que barrer también la familia.

Hueco FLAGGED, no acuñado: el ejemplo viejo de píldora / fondo de trigger no tiene traducción a pomos — la receta clava el block-size al grosor. Queda en §13 de next-features.md, sin inventar un eje de forma.

Abre expediente: §14 de next-features.md — unificar tabs bajo la misma adjudicación (--indicator-{x,y,w,h} SIN prefijo, escritos por la capa compartida MeasuredIndicator, 2 entradas global en el ledger). Redactado, NO ejecutado.

FIRMA EJECUTADA (2026-08-26) — la ley del espacio cerrado

Qué se firmó: el espacio de nombres público es CERRADO sobre el contrato — un --{c}-* existe si y sólo si base.ts lo declara, no hay tercer estado. Corolarios: el fallback es dispositivo de seguridad de VALOR, no escapatoria de contrato; declarada la clave, el fallback inline sobre el nombre público PROPIO muere (el default vive en UN sitio); el guard de fantasmas pasa al «si y sólo si» — un --{c}-* propio fuera de contrato es rojo, con o sin fallback. El vocabulario AJENO (préstamo de otro componente) y el de sistema conservan sus reglas: el guard filtra por prefijo propio y no los ve.

Lo que el barrido previo encontró — y por qué importa: no eran dos componentes. El catálogo tenía 25 nombres propios con fallback fuera de contrato en 10 componentes (27 sitios), y se parten en DOS CLASES:

  • 11 knobs de tema de verdad → declarados: mockup ×9, text-scramble ×2.
  • 14 CANALES DE VALOR que soma o el envoltorio escriben inline por instancia → NO se declaran (declararlos revertiría la firma medida del 2026-08-23 que retiró --color-swatch-fill del contrato) ni se renombran aquí (es codemod de otro eje). Van a un REGISTRO nombrado y fechado dentro del propio test, PENDING_PRIVATE_RENAME, con razón y destino por entrada. + --motion-stagger-each-default, que no es canal (nadie lo escribe): su default pertenece a EidosConfig.motion. 15 entradas.

El registro es el patrón ledger de la casa: no bendice, cuenta, y sólo puede menguar — una entrada que deja de describir un consumo (renombrada, declarada, muerta) se reporta STALE y sigue roja hasta borrar la línea.

La mentira que la ley mató, en concreto: --mockup-shadow cargaba DOS defaults bajo un nombre — var(--shadow-4) en el marco, var(--shadow-5) en el bisel del teléfono — así que un tema que lo moviera no podía saber cuál de los dos tocaba. Se acuñó --mockup-phone-shadow: superficie distinta, nombre distinto. El TSC no podía expresarlo de otro modo (sus ejes de scope son cerrados y data-chrome no es uno).

Neutra en píxel, medida dos veces: el diff de generated/ son esas 11 líneas con su default verbatim y NADA más; y el A/B en navegador — cada token nuevo forzado a initial con las declaraciones viejas restauradas inline — dio computed idéntico en los tres mockups de /blocks/feature-split (ambos cromos; el bisel conserva shadow-5 y el marco shadow-4, que era justo lo que la acuñación protegía) y en text-scramble. ⚠ mockup no tiene ruta de demo propia: se mide donde de verdad monta, en /blocks/*.

Números: no contract entry 18 → 16; todo lo demás QUIETO — reach 73 %, 65 al 100 %, ledger 1154 · 0 NEW · 0 STALE, audit 162 PASS / 4 NEEDS-WORK idéntico, --names 0 desviadas, docs:check 0/0. El suelo NO sube: la ley cerraba un hueco de CONTRATO, no de alcance (esas declaraciones ya puntuaban public — lo que faltaba era declarar el NOMBRE).

Muta-prueba de tres caras: var(--mockup-inventado, red) (propio, CON fallback) → rojo nombrando la clave · el mismo inyectado con un nombre AJENO y fallback → verde · una entrada del registro que deja de describir un consumo → rojo STALE nombrándola.

Efecto lateral que hay que saber: el crudo var(--primitive-neutral-12) migró verbatim de mockup.css:73 a base.ts como default de mockup.phone-bezel. Sigue siendo deuda R-4.6 — pero la regla escanea CSS de componente y ya no lo ve, así que la fila de mockup perdió ese error sin que nadie lo arreglara. Queda anotado en la propia clave y en next-features.md; mockup sigue NEEDS-WORK por su E-2.3 (README ausente).

FIRMA EJECUTADA (2026-08-26) — los 318 privados no-derivados

Qué se firmó: la «clasificación de los 318», en tres piezas — e812d3ffd (F1: el censo aprende a MEDIR puente y canal) · a74aeec7b (F2: el residuo adjudicado clave a clave) · este commit (F3: el suelo sube y la prosa se salda). Cierra el agujero que la cabecera del ledger nombraba: la tercera clase no-alcanzada ya no vive sólo bajo el reachPct grueso.

La aritmética, sin resto: 318 = 132 puente + 36 canal + 84 anotados + 66 al ledger. Las dos primeras las decide la MEDIDA (el puente contra la salida real de renderRecipePaletteForward, no contra la forma del nombre; el canal contra quién escribe el privado — nadie lo declara, lo pone soma o el envoltorio por instancia); las otras dos son actos: la válvula nueva /* private: <razón> */ sobre la declaración (clase exception, la de siempre, no un tier nuevo) y una entrada de ledger con private como DebtClass de pleno derecho, con newDebt y STALE simétricos.

Los cuatro números que quedan vigentes: reach 73 % (3074/4228 — el numerador NO se movió, cayó el denominador 4480 → 4228) · 65 componentes al 100 % · ledger 1154 claves (763 global + 325 literal + 66 private) · 162 PASS en component:audit, idéntico.

Las cuatro contradicciones con ficha que la medida destapó (F2 las dejó señaladas; ninguna se resolvió a ojo):

  • metrics — su §5 firmado decía que el conmutador del icono derivaba ENTERO de públicos. Cierto para track/text, falso para solid/contrast, que leen roles crudos. Resuelto POR MEDIDA: 3 anotados, 2 al ledger.
  • timeline — su §5 es pre-B′ y se respetó tal cual (10 knobs, 5 notas). Queda SEÑALADO para reabrir si la escalera de paleta de B′ pesa más que aquel veredicto.
  • proof-of-human — 17 knobs adjudicados por DOS notas, sin §5 propia que lo dijera: la base del alias es verbatim la que el forward declara para ese slot (medido dos veces). El alias --_proof-of-human-accent{,-soft} es borrable a coste cero de píxel — leer el puente directo clasificaría bridge sin anotación; registrado como trabajo futuro en §13.
  • listbox — sus 6 knobs (5 notas) dicen «adopto la capa list-surface», y son reversibles el día que §13 le dé receta propia a esa capa (diez componentes de golpe).

FIRMA EJECUTADA (2026-08-25) — «la mitad gratis»: cegueras 4 y 5, coste de ledger CERO

Qué se firmó (047464389): las dos piezas del expediente VA que NO compartían el coste caro — el codemod ciego a la IIFE con sus dos hermanos, y los cinco huecos de la sonda más el sexto propio (el pase de FOCO prometido e inexistente). Tres ficheros, ni una entrada del ledger. Ejecutada por cuatro agentes (tres constructores + uno ADVERSARIAL con mandato de refutar), con bases capturadas ANTES de editar.

Los números: centinela label 1/2 y switch 27/52 byte a byte idénticos a las bases pre-firma; sonda de card 0 diffs en sus 7 bloques viejos; editable estrena bloque open (25 filas, 6 con data-editing) y dos corridas sobre código idéntico dan 0 diffs en TODOS los bloques; menubar abre por la entrada del centinela (6 nodos → 7, con el panel) y chat-message 24 filas × 2 corridas = 0 diffs; codemod 145/145 bloques, avatar depth 3 con 97 claves (86 públicas + 11 privadas), laboratorio sobre copias (duplicado inyectado DETECTADO, renombrado = 1 línea); importar el centinela: 0,85 s sin navegador.

Las decisiones que deja escritas:

  • COMPONENT_OVERRIDES es UNA tabla: la sonda la importa y OPEN_TRIGGER se poda de sus copias literales. La guarda de entrypoint lleva realpathSync — hallazgo del adversarial: sin el realpath muere a través de un junction (exit 0 sin inspeccionar nada), y este repo usa worktrees con junctions.
  • La vía de carga del codemod SE QUEDA en tsx: «el censo corre con node pelado» era FALSO (todo src/uix/morfo importa sin extensión).
  • prepareWith en la sonda sólo corre sin fila en DEMO_VARIANTS (re-clicar APAGA los checkboxes que prepare() ya encendió) — y eso deja a la vista la deuda de search-field (§13).
  • El no-determinismo de chat-message quedó ACOTADO al bloque rest de la sonda (13 vs 16 nodos, chips sin cuajar al medir) — dato para la GRANDE.

Lo que quedó fuera, con su registro en §13: el barrido de variantes de la sonda (ensanche diferido por el propio expediente) · el clear-trigger de search-field en el pase open · la asimetría de escenario de los seis con prepareWith sin fila · el cortocircuito alreadyOpen que la sonda no tiene · y TRES lectores independientes de la estructura de base.ts (codemod, censo, centinela — la próxima forma nueva se enseña tres veces). El catch del prepareWith del CENTINELA sigue MUDO a propósito: es la ceguera 6, S1 de la GRANDE.

FIRMA EJECUTADA (2026-08-25) — «la GRANDE»: el guard deja de estar ciego, en tres barridos

Qué se firmó (6392d6003 · b4dd5a906 · b9cef9a43): las cegueras 6+1+3+2 del expediente VA, UNA firma, TRES barridos del ledger ENTERO (91 componentes cada uno), ejecutada con el mismo esquema de agentes que «la mitad gratis» (tres constructores en serie + adversarial con mandato de refutar: 12/12 claims confirmados).

  • S1 — el prepareWith GRITA (lo que cae en su catch es un control que estaba y falló — la fuente de las muertes fantasma) y el STALE ve los PATRONES (reasonFor, mensaje que distingue «retira la exacta» de «ESTRECHA el patrón»). Barrido: 91/91 verdes, 40,8 min, 0 avisos. Cosecha: los dos patrones PLANE_SUPERSEDED de toast (18 claves), rancios desde la noche de §12.9, RETIRADOS con 3/3 de reproducción.
  • S2 — el chooser tipa la sonda por el VALOR por defecto leído de base.ts, no por el nombre (357 claves cambian de sonda; 320 de la clase A «vivo por accidente» IACVT). MONÓTONO medido: 91/91, sólo avatar +1 y stepper +1, sus dos exactas retiradas. Ni % ni enteros se clasifican (tipar el 0 habría matado en falso 7 claves vivas).
  • S3 — tono+estado: tono en el pase de hover · tono MEDIAL (^|-) (las 16 de tag-group) · hover para claves de tono muertas (las 21 de file-upload) · seis ejes de estado como DATO, valores del morfo/receta/DOM real. Barrido: 91/91, 43,8 min, exactamente los 9 previstos, +140 claves (catálogo 2776 → 2918, 78 % → 82 % de claves vivas). 130/132 TONE_UNREACHED reviven; las 2 que no eran PALETTE_FLOOR (el tono HOST por defecto — test de dos caras en checkbox y radio-group, firma idéntica en los ocho componentes).

El ledger después: 10 patrones retirados + 2 estrechados + 7 exactas retiradas + la constante TONE_UNREACHED RETIRADA (la muerte que su docstring pedía). Tier de patrones 28 → 16, 15 de los 16 son el único hecho PALETTE_FLOOR, y el adversarial verificó que los 16 cubren SOLO claves muertas — cero zombis — y que ninguna clave muerta quedó sin razón (659 muertas, 0 sin adjudicar).

Dos correcciones al expediente, reproducidas dos veces antes de tocar: las -text de tags-input SÍ reviven (la causa del arquetipo sólo tapa el ítem RESALTADO; la escena no tiene ninguno, y el token pinta además dos nodos hermanos fuera de esa regla) · tag-group.hover-item-fg la callaba su data-state='selected' nativo — el nodo es un <a data-archetype='trigger'> que ninguna regla de arquetipo toca.

Deuda nueva registrada en §13 (adversarial, ninguna bloquea): la asimetría del estampado (data-color en TODOS los nodos vs sólo el proveedor en producción — medido que NO es la causa de ninguna revivida) · el ternario falsy de la restauración de atributos (un attr="" se borra en vez de restaurarse; hoy inocuo) · checkbox.stroke-ease con sonda de color (única de las 51 de easing) · el residuo del recorte del hover (button.palette-element, escrito en su propia razón).

EL CIERRE, EJECUTADO (2026-08-25) — deuda B + clase structural + R-5 a error con LEDGER

✅ EJECUTADO con el mismo esquema de agentes (tres constructores + un adversarial que confirmó 11 claims y refutó dos números de prosa). Cinco commits: 2c61946fb (clase structural) · ef47f3447 (deuda B) · 69d69d43c (el ledger de deuda) · 1ba518fcc (correcciones del adversarial).

  • Deuda B saldada: proof-of-human (20 claves derivadas) · audio-player (README nuevo; es un skin sobre media-player que consume sus 61 claves y añade 8; sin demo propia a propósito) · palabras (sección honesta SIN tabla: sin contrato en base.ts, y sus 117 nombres --palabras-* son la proyección del scheme-style del documento — canal de VALOR, otro eje; la clasificación del censo sobre él queda EN CUESTIÓN, §13).
  • Clase structural: los cinco 0 %-por-naturaleza (aspect-ratio · text-blur · cascade · motion · date-picker) salen del denominador con su razón EN el artefacto (forma LAYER_VOCABULARY). Reach 68 → 69 %, no-contract 23 → 18, el gate deja de ser inalcanzable por construcción.
  • R-5 a error con LEDGER DE DEUDA — la firma que rediseñó F3: la deuda real (1088 claves: 763 global + 325 literal, carriles WIP DENTRO) se registra UNA vez por clave en scripts/theming-census-debt.ts — deuda registrada, NO excepciones; la válvula sigue intacta y nunca ciega el ratchet. R-5.1/5.2 a error contra el ledger (R-5.3 ya lo estaba): deuda nueva = rojo NOMBRADO, entrada rancia = STALE hasta borrarla. Los techos burdos del suelo RETIRADOS (el ratchet es por clave); atHundred corrige su criterio (public > 0, 14 → 45). El gate de F3 queda redefinido honesto: «censo 100 % ADJUDICADO» — y está EN VERDE.
  • Pendiente de TU firma, nombrado en §13 y en la cabecera del ledger: los 318 privados no-derivados — ✅ FIRMADA Y EJECUTADA 2026-08-26 (e812d3ffd · a74aeec7b + F3; ver §«FIRMA EJECUTADA — los 318» arriba) · el idioma var(--{c}-x, fallback) sin contrato — ✅ FIRMADA Y EJECUTADA 2026-08-26 (§«FIRMA EJECUTADA — la ley del espacio cerrado» arriba) · QUEDA la re-clasificación de palabras (117 nombres --palabras-* puntuados public que son canal de VALOR) y las DOS deudas de eje propio que la ley nombró (14 canales → --_{c}-* · cableado EidosConfig.motion), ambas detalladas en next-features.md. · Deuda menor registrada: las fichas de docs/audit/theming/ esperan un --report supervisado post-structural (tarea aparte).

FUERA del carril de theming (registrados HOY, no se ejecutan desde aquí)

  • E1 — los botones del array de form COMPONEN el Button (clase A-112): expediente completo de VC en exped/vc/ + la corrección de E-talla: E2 ahorró el CONTEXTO pero no la LECTURA — E1 debe pasar size al Button que inyecte o es la regresión medida (26→36 px en xs). Y las partes son MUDAS (morfo sin evento; verbos canon: commit-set-add/commit-remove).
  • Lo abierto por el ARIA: tag-group filas SIN celdas (el «precedente» refutado midiendo) · search-field F-1 · la regla del censo de citas APG.
  • El párrafo anchura-vs-densidad querría vivir TAMBIÉN en reference.md junto a la norma tapada (dicho por E-talla, fuera de su alcance).

Las leyes que dejó el día (sesión 2)

  • Una interrupción del autor MATA a los agentes en vuelo — informe.md a disco INCREMENTAL siempre; el disco sobrevive (V1 se recuperó íntegro).
  • El supervisor MIRA las capturas antes de cada commit (regla del autor).
  • Las demos del sistema NO entran ni como prueba central de una firma (reafirmada sobre la captura del velo; la prueba citable es el barrido de las 14 identidades del catálogo).
  • F5 compartidas se RETIENEN y liberan EN SERIE cuando varios ejecutores comparten next-features/CONTINUE — tres ejecutores hoy, cero pisadas.
  • La sonda estándar ganó DOS mentiras registradas (se pisa el data-size forzado sobre nodos compuestos · los clamp() fluidos no comparan sin declarar viewport) — están en §«Lo que el instrumento aprendió».

Lo que queda de firma vieja NO cambia: la lista «Lo que espera TU FIRMA» de más abajo quedó VACÍA el 2026-08-25 y las entradas EJECUTADAS de hoy están intercaladas tras esta sección.

FIRMA EJECUTADA (2026-08-25) — el velo del sistema no muere por un ATAJO

Qué se firmó (changelog §56, reverso de §55 el mismo día): al bajar el sobre de trigger de popover.css al suelo, §55 lo dejó a (0,0,0) — el mismo peldaño que archetypes.css, que es quien pinta el velo de estado del sistema. Y el suelo declaraba su superficie con el ATAJO background:, que expande a background-image: none — la propiedad exacta donde vive ese velo. Resultado: el velo de hover de todo trigger desnudo dependía de qué hoja cayera después. No sólo por la regla de hover: el :where() deja también la de REPOSO empatada con la de hover del arquetipo, y ésa sola bastaba. La corrección son DOS PALABRAS — background-color: en popover.css:60 y :75.

Los números: 0 px en el barrido de las 14 identidades del catálogo que montan un [data-popover-trigger] (68 lecturas en reposo → 0 movidas, 68 en hover → 0 movidas, rules split=2 en las 14) con control negativo diff 5 / 4; las dos sondas del eje a 0 diffs (popover 448 valores / 8 estados, chronos 36 832 / 7); y el velo sobrevive al orden invertido, donde con el atajo moría (linear-gradient(…8 %…) → none). Cae con la firma el pin chronos.css:417 (cinco declaraciones a (0,3,0) que ya no defendían nada): 0 diffs en 20 combinaciones variant × size, control negativo 20/20 — §13 lo había adjudicado mal, al velo del +N more lo mata button.css:69/:88. Guarda §55 apartado (f) en active-eidos-config.test.ts, 4 mutaciones / 4 mordidas (las dos primeras muerden la mitad positiva, las dos segundas —el atajo AÑADIDO junto al longhand— la negativa).

Lo que quedó fuera, con su número en next-features.md §13: el residuo transition del MISMO empate (suelo background, border-color vs arquetipo opacity, background-color, las dos a (0,0,0) — hoy el border-color del hover de popover salta en vez de animarse), la decisión de producto «popover deja de declarar hover propio» (no es coste cero: 0.9821 → 0.931 más el velo hoy, sólo el velo después) y calendar-select.css:14, que no es un pin — retirarlo rompe el tipo en 3 de 4 tallas (12 / 14 / 20 → 16 px).

Fichas y prosa saneadas: chronos.md (censo 202→198, propuesta 50→47, en bloques <!-- mano:… --> para que sobrevivan a --report), calendar.md (su ⚠ HALLAZGO «no determinista» lo cerró §55 y ya no apunta a §13), emoji-picker.md y los tres comentarios de código que seguían afirmando (0,2,0) del sobre (emoji-picker.css, gradient-picker.css —sólo su mitad base, la de estados sigue viva— y lib/recipes/base.ts). Se ANOTAN, no se reescriben: cada medida fue correcta con la cascada de su día.

FIRMA EJECUTADA (2026-08-25) — el eje size de form LLEGA a sus acciones

Qué se firmó: ceder el cromo de submit/reset al Button compuesto (firma del 2026-08-24) destapó el reverso. form-submit.svelte:20 y form-reset.svelte:15 traían su propio size = 'md' y nada les pasaba la talla del formulario, así que un <Form size="xs"> los renderizaba con data-size="md" —36 px de alto y 16 px de letra— en las cinco tallas; del eje sólo sobrevivía el row-gap. Quitar el default no arreglaba nada (el Button tiene el suyo): hacía falta el puente, y el precedente es literal. Nuevo eidos/components/form/context.ts calcado 1:1 de toolbar/context.ts (Symbol('form-eidos-ctx') + getter de propiedad), form.svelte publica el resolvedSize que ya calculaba, y las dos acciones leen size={size ?? form?.size} — la prop explícita gana, y standalone cae en el md del propio Button sin re-declararlo. Sólo eidos: cinco ficheros, cero morfo / soma / sema; el contrato de 64 claves y el ledger, intactos.

Los números: alto −10 / −6 / 0 / +8 / +16 px y letra −4 / −2 / 0 / +4 / +12 px, sobre submit y reset por igual; md no mueve un solo valor; 84 diffs en total y cero en los cuatro nodos del array, el widget y el resumen de errores. Sonda estándar antes/después 0 sobre 832 valores en 7 estados; instrumento propio reproducible (dos pasadas sobre código idéntico ⇒ 0 diffs). vitest src/uix/eidos 443/444 (el rojo es el ajeno conocido skin-media-player), check 0 errores atribuidos a los cinco ficheros, docs:check 0/0.

La doctrina que hubo que adjudicar: VERBATIM, no la norma tapada en md. La tapa de reference.md §«Container→part derivation» gobierna contenedores cuyo eje es ANCHURA (su primer consumidor es Dialog.Close); el de form es DENSIDAD declarada, y su propia receta ya había escrito los cinco peldaños distintos de la acción. Escrita en next-features.md §13 §«Anchura vs densidad», con el corolario de que a alert-dialog —mismo agujero, eje de anchura— le toca la TAPADA.

Lo que quedó fuera, con su entrada en §13: alert-dialog (no medido), Form.ErrorSummary con un data-size que ninguna regla lee, la escalera de fuente 12·12·14·14·16 de la acción de form (que corrige E1 en su carril) y la cita colgada de dialog/context.ts:9.

FIRMA B′ EJECUTADA (2026-08-24) — la cascada de paleta es una ESCALERA

Qué se firmó: los tres escritores de --{c}-palette-* pasan a tres peldaños que no empatan — suelo :where([data-{c}]) (0,0,0) < forward [data-{c}]:where([data-color], [data-color-custom]) (0,1,0) < tono [data-{c}][data-color='X'] (0,2,0). Antes el forward genérico se emitía el ÚLTIMO empatado a (0,2,0) con los bloques por tono, así que ganaba por orden de emisión y 419 claves públicas de tono en 49 recetas no pintaban nunca.

Por qué B′ y no reordenar la emisión (que es lo que §13 proponía): el orden es contrato invisible —no se lee en el artefacto, no lo verifica un guard sin recorrer la hoja entera, se rompe moviendo una línea—. La especificidad es contrato EN el artefacto. Y es la misma ley que §12.9 acababa de firmar para el plano de profundidad: una doctrina aplicada dos veces, no dos parches.

Los números: neutra en píxel sobre ~297.000 valores computados comparados antes/después (70.912 + 34.336 planos en dos lotes, 57.720 en el tercero, 134.464 con los tonos ESTAMPADOS en las 49 unidades) — 0 diffs reales, y los 17 crudos probados ruido reproduciéndolos sobre código idéntico. 207 de 353 claves de tono volvieron a mover un valor computado. Guarda por mutación en active-eidos-config.test.ts:1761 (6 mutaciones, 6 mordidas; cierre estructural: toda declaración de paleta vive en uno de los tres peldaños; anti-vacío > 40). Contrato del forward ensanchado de 2 a 49 recetas — vigilaba sólo las de paleta pública y era ciego a las 47 privadas. Suite 35/36 (el rojo skin-media-player es AJENO y conocido).

Dónde vive todo: doctrina en theming/changelog.md §53 · contrato en theming/reference.md (bloque THM-2) · emisor src/uix/eidos/lib/render-css.ts (isPaletteSlotToken parte el bucket host — sólo las ranuras de paleta bajan, el chasis se queda a (0,1,0)) · guarda active-eidos-config.test.ts · ledger scripts/theming-sentinel-exceptions.ts.

Tres leyes que deja escritas, y las tres costaron una medida:

  1. El silencio del guard NO es señal cuando la adjudicación es por PATRÓN. theming-sentinel.ts detecta STALE sólo contra el ledger EXACTO (k in ledger), nunca contra SENTINEL_PATTERN_EXCEPTIONS. Las 207 claves resucitadas no dispararon ni un aviso: el estrechamiento hubo que hacerlo comparando a mano contra las 13 entradas. La única que el guard sí vio fue editable.risk-border, que era exacta.
  2. Un tono por defecto ESTAMPADO resuelve por el forward, no por el suelo. El envoltorio estampa data-color en toda instancia, la default incluida, así que <Button color="primary"> lee el forward y --button-primary-solid no pinta. Quitar el atributo ES hablar en silencio, y entonces sí pinta (medido: oklch(0.5556 0.1829 305.86) → rgb(1, 2, 3)). Eso es PALETTE_FLOOR; el resto de supervivientes son TONE_UNREACHED (el guard no sabe combinar el estampado del tono con el hover ni con la variante). Ledger 17 → 28 patrones. (Anotación 2026-08-25: la GRANDE retiró TONE_UNREACHED — el guard ya combina; queda PALETTE_FLOOR y el tier volvió a 16.)
  3. Un 0/N se investiga, no se reporta. switch dio 0 de 12 claves de tono vivas y button sólo 14 de 49 — y ninguna de las dos era la cascada: la ficha de switch monta UN switch y arranca unchecked (y todas sus ranuras cuelgan de [data-state=checked]), y button no declara COMPONENT_OVERRIDES, así que no se barre data-variant y su ficha monta UN botón sólido. Forzando el estado, las dos alcanzan. Es la ley 2 del §«Lo primero que debes saber», confirmada una vez más.

Lo primero que debes saber mañana

  1. ⚠⚠ EL ÍNDICE DE GIT ES COMPARTIDO. Un solo .git/index para todas las sesiones de este worktree. Verificar con git diff --cached NO basta: entre que miras y confirmas, otra sesión puede reescribirlo. Verificación y git commit van ENCADENADOS en la misma invocación de shell. Costó un commit con trabajo ajeno dentro y un ledger sin parsear durante cuatro commits de HEAD. Y me lo hice yo mismo una segunda vez con un git add derivado de git diff en vez de rutas explícitas.
  2. El instrumento miente antes que el código, y hoy mintió cinco veces. La sonda no congelaba nada · el guard mide UN nodo en siete portalados · un árbol de trabajo rancio da un 0/98 falso · un grep con la frase exacta encuentra 2 de 7 · y una lista escrita por Python en Windows lleva \r y revienta el barrido entero con «todos fallan igual». Un 0/N o un «todos igual» no es un resultado: es el instrumento.
  3. Antes de retirar una clave, mira quién la lee (paso 8.bis de §7.3, nuevo). scroll-area.auto-max-block leía muerta en su demo y la consumen CINCO recetas ajenas. Retirarla habría roto seis componentes sin que un guard se pusiera rojo.

Qué hay para hacer, por orden

  • Nada de tokenización. Si aparece un componente nuevo, el protocolo §7 del PLAN es la ley y el brief de agentes vive en el scratchpad de la sesión.

  • Deuda B — 3, cada uno por su razón: audio-player (no tiene README de eidos donde escribir «Talla y tema»), palabras y proof-of-human (carriles propios).

  • Deuda de instrumento, registrada en §13 y sin bloquear a nadie: el guard congela transition pero NO animation (la sonda ya sí) y no fotografía transform/translate; eidos-lint no conoce las exenciones que el test de vitest sí tiene; la columna size del censo se apaga cuando el eje sube al TSC. Las tres piden re-verificar el ledger entero, así que son firma pequeña.

  • Lo que espera TU FIRMA (todo medido, ninguno bloquea):

    1. La cascada de paleta — ✅ FIRMADA Y EJECUTADA 2026-08-24 como firma B′: era la pregunta hermana de §12.9 y se resolvió con la misma herramienta, :where(). Todo (doctrina, contrato, emisor, guarda, ledger) está en §«FIRMA B′ EJECUTADA» de aquí arriba. No queda nada que firmar.
    2. El componente COMPUESTO contra la receta que lo compone — ✅ FIRMADA Y EJECUTADA 2026-08-24: el bloque de acción de form.css apunta ya sólo a los dos botones del array de AutoFields, submit/reset ceden todo su cromo al Button que componen (0 diffs medidos) y mueren sus cuatro forwards _palette-*; las 23 públicas se quedan, re-adjudicadas como rama que la demo no monta. gradient-picker (36) y emoji-picker (3) ya estaban ejecutados (2026-08-20 y 2026-08-23, verificado el 2026-08-24: hoy ninguno adjudica una sola clave por supersesión del compuesto). Salida barata ya probada: toolbar cede el cromo al Button y no pierde ni una clave, porque su receta no escribe la regla. Se ENSANCHA a la TINTA (verificado 2026-08-24): la misma firma §5.2 del harness cubre ya el color de code. Medido por CDP (getMatchedStylesForNode): sobre [data-code] sólo DOS reglas declaran color — [data-code] (0,1,0) y [data-uix-docs] code (0,1,1, web/routes/uix/uix.css:792, var(--uix-text) = rgb(24,24,27)) — y gana el harness. Con data-color='risk' estampado, --_code-palette-text y --_code-color SÍ se mueven, pero color no: la cadena del componente funciona y la pisa la del harness al final. No es entrada nueva: es el mismo expediente que el ledger ya cita para code.radius (theming-sentinel-exceptions.ts:665) y code-block.pre-padding (:676), y que reaparece por tercera vez en prose.code-bg / code-radius / code-font-family (:862-865). Lo que cambia es el ALCANCE de la firma: de radio / padding / tipografía a incluir también la tinta.
    3. file-upload barre DESCENDIENTES — ✅ FIRMADA Y EJECUTADA 2026-08-24: la mitad descendiente pasa a cinco selectores por parte propia (el morfo declara data-disabled en 6 nodos y los 6 llevan su attr de parte, así que el barrido era redundante hacia dentro). Única ocurrencia en 171 recetas, no 162. file-upload e image-picker con diff CERO; chat-composer recupera 6 valores —el textarea salía a 0.4³ = 0.064 de alpha en el tema POR DEFECTO, 925 px— y su entrada del ledger se RETIRA. Detalle en §8 del PLAN.
    4. Las cuatro claves de avatar fuera de gramática — ✅ FIRMADA Y EJECUTADA 2026-08-25 (opción A-c): las cuatro no eran la misma cosa, y la medida las separó en tres tratos. UNA se renombra — badge-color-custom-contrast → badge-fg-custom-contrast, la única tinta de verdad (alimenta color:) y alcanzable desde :root (medido: white → rgb(4,5,6)); es la salida literal del clasificador firmado, que deja el calificador contextual DETRÁS de la ranura como en el precedente scrim-color-on-dark → scrim-fg-over-dark. DOS se retiran del contrato — ring-color-custom y badge-color-custom son canal de valor: la puerta data-*-custom y la escritura inline nacen de la MISMA expresión del envoltorio, así que no existe ni puede existir instancia en la que un tema les gane. Pasan a privadas --_avatar-{ring,badge}-color-custom escritas por el envoltorio y leídas con var(…, currentColor) — la forma que background, carousel, checkbox-group, file-upload y gradient-builder ya usan (5 de 5 privadas de envoltorio NO se declaran en la receta), y la adjudicación que tabs hizo con su indicador. UNA queda EXENTA — group-carve-color alimenta box-shadow: es la GEOMETRÍA del carve, no tinta, y group-carve-fg sería gramática correcta con semántica peor. Contrato público 88 → 86 (medido en vivo: la pestaña se llama ya «Tokens 86»), censo --names 4 desviadas → 0 en TODO el catálogo (4556 claves, exentas 13 → 14). Sonda §7.4: 0 diffs sobre 576 valores, y el píxel de los nodos con puerta idéntico (anillo ámbar rgb(245,158,11), insignia violeta rgb(124,58,237), tinta white), incluida la «instancia imposible». 0 píxeles, como firmaba la opción. Abrió dos deudas de INSTRUMENTO, las dos en next-features §13: el codemod de nombres aborta sobre avatar (blockRange() no lee la IIFE, la misma ceguera que f68bac4a6 arregló en censo y centinela), y el centinela elige el valor de sonda por el NOMBRE (fg$, sin -fg-), así que las cinco claves del catálogo con fg MEDIAL reciben 1234px. Detalle en §8 del PLAN.
    5. tags-input sella aria-selected='true' como LITERAL en cada etiqueta — ✅ FIRMADA Y EJECUTADA 2026-08-25 (opción B-a2): el morfo ata aria-selected al estado que soma YA deriva de highlightedIndex (v.literal('true') → v.stateRef('active')), una línea. Era el único aria-selected incondicional del catálogo. El árbol AX pasa de dos option con selected: true dentro de dos listbox con multiselectable: false a cero en reposo y exactamente uno navegando (medido por CDP). --tags-input-item-fg revive —el centinela sube a 71/91 y su entrada del ledger se RETIRA— y la etiqueta recupera la afordancia de hover: reposo y hover eran byte a byte IDÉNTICOS (media RGB 212,10 en los dos) y ahora son 224,58 → 212,10. Diff de sonda: 14 valores de 2.504, todos background-image de las dos etiquetas en los siete pases sin puntero — el velo de estado saliendo del reposo, y nada más. Detalle en §8 del PLAN. Lo que NO arregla: la tinta de tono de la etiqueta activa. Enmienda del 2026-08-25: esa tinta ya no está abierta en §13 sino en next-features §12, como cuarta ocurrencia de §12.5 y con su decisión ya redactada (docs/next-features.md:516) — es de la capa del SISTEMA, no del componente. Y las dos cifras de esta entrada estaban mal: la especificidad es (0,4,0), no (0,3,0), y el ganador no es el selector de data-highlighted sino el BLOQUE archetypes.css:245-257, cuyos dos selectores —data-highlighted y aria-selected— casan LOS DOS la etiqueta activa desde esta misma firma; retirar uno no mueve nada. Los dos listbox anidados que esta entrada describe ya no existen: la firma «el árbol ARIA de tags-input habla UN patrón» (2026-08-25, F-1 de la ficha de componente) los sustituyó por el layout grid del apg —grid > row > gridcell, el campo un textbox en su celda— sin mover un píxel: 0 diffs sobre 2496 valores computados en los 8 pases de la sonda, y ningún CSS del ecosistema selecciona por role. No es theming y no toca el ledger de claves; el centinela no se re-corrió en esa firma (su script está en obras en otra sesión), pero no hay valor computado que se haya movido.
    6. El chip de añadir reacción de chat-message lo pinta popover.css: los chips de reacción son píldoras redondas y el de añadir un cuadrado gris. — ✅ FIRMADA Y EJECUTADA 2026-08-25 (opción b, «la ley del suelo»): el arreglo NO fue de chat-message sino del sobre de popover.css, que se autodescribe como «a baseline button envelope» y estaba a (0,2,0) con su :hover a (0,5,0). Baja a :where(...) — (0,0,0) — reposo y hover. Tercera aplicación de la ley de §12.9 y de B′/§53, y la primera que cae en una receta escrita a mano. 76 instancias en 16 rutas, cuatro estados: 69 no mueven un valor de receta (los 64 botones desnudos del sitio de docs, gradient-picker, la demo data-perm-step y los tres field-trigger excluidos) y 7 mueven, las 7 hacia lo que su propia receta declara — el chip vuelve a ser píldora (34×26, radio 9999, 12px), chronos recupera su plain, calendar pasa a ghost y deja de ser no determinista (reproducido antes: 2 de 6 cargas en 14px en vez de 16px; después: 8 cargas en dos órdenes, el mismo píxel), emoji-picker, palabras y natural-time-picker recuperan lo suyo. 0 movimientos en 167 nodos de referencia. Centinela: natural-time-picker 44/62 → 48/62; ninguna entrada del ledger salió STALE (ninguna estaba adjudicada por este sobre). Cierra de paso el empate latente de chat-message.css:462. Guarda por mutación en active-eidos-config.test.ts (§55, 6 mutaciones, 6 mordidas). Doctrina en theming/changelog.md §55; lo que queda abierto —los dos estados a (0,3,0) y los pines redundantes— en next-features.md §13.

    CON ESTO LA LISTA QUEDA VACÍA: no espera NINGUNA firma tuya en este eje. Los seis puntos están ejecutados. Lo que siga tiene que abrirse como entrada nueva, no heredarse de aquí.

Lo último que se hizo (2026-08-24 noche): §12.9 FIRMADA

El plano de profundidad es el SUELO, a especificidad cero. La Decisión 8 daba por bueno el empate a (0,1,0) porque «las recetas cargan después de la fundación y ganan por orden de cascada» — y esa premisa era FALSA: la fundación se inyecta en runtime y las recetas llegan como chunks de Vite, así que ganaba quien cargara el último, medido AL REVÉS en dev que en producción. Una moneda al aire, no una decisión.

La regla de apariencia del plano se emite en :where(...): la receta gana donde el componente HABLA, el plano pinta lo que CALLA. El frost NO baja — es un opt-in explícito por elemento y debe ganarle al fondo del componente.

Restauró: los seis tonos de toast (4 de 4 distintos donde antes 3 de 3 eran idénticos, y la franja de acento de 0,67 px de gris a 3 px del color de la intención) · las tres variantes de tooltip · 17 claves que salieron STALE solas del ledger. Movió: 16 de 19 adoptantes con diff CERO sobre ~49.000 valores; los tres restantes son la firma trabajando, incluido el line-height que la receta de link-preview tenía escrito como pendiente de esta firma.

⚠ Para medir el plano: la sonda compartida mide UN nodo en los portalados y nunca abre la superficie. El «antes» se reproduce EN RUNTIME inyectando la regla vieja a (0,1,0) (scripts/__plane-open.mjs), nunca revirtiendo el fichero.


LO ÚLTIMO (2026-08-24): DOCE componentes en paralelo, con SUPERVISIÓN. chat-log 77 → 100 % (fd8f01bac) · chat-composer 79 → 100 % (71a097ddc) · rating-group 80 → 100 % (f6a618328) · toggle 81 → 100 % (edc019515 + 007c2af52) · select 83 → 92 % (cc010f79a) · avatar 75 → 90 % (f68bac4a6) · calendar 84 → 89 % (509d1144f) · file-upload 76 → 83 % (0c06d42aa) · editable 75 → 82 % (2117d7422) · switch 76 → 81 % (bbdc77c07) · radio-group 77 → 81 % (f5d252443) · card 78 → 78 % CERRADO (f3189a05a). Global 68 %.

Los doce se supervisaron uno a uno, y la supervisión encontró cosas. Refutación estática de los doce commits (cero líneas ajenas en el generado), centinelas re-corridos, y cinco hallazgos propios que están abajo. El protocolo §7.7 no es un adorno: cuatro de los cinco salieron de él.

Lo que enseñó esta tanda, en una línea cada una: el ÍNDICE DE GIT ES COMPARTIDO y verificar el árbol indexado NO BASTA · un árbol rancio hace MENTIR al instrumento sin tocar un commit · la cascada de paleta se lleva TAMBIÉN el tono por defecto · un componente puede BARRER DESCENDIENTES y comerse el token de sus huéspedes · y la sonda y el guard MUEREN con --import tsx/esm.

HISTÓRICO (2026-08-23, tarde): SIETE componentes. navigation-menu 44 → 100 % (85988785d) · waveform 55 → 86 % (11b92ded6) · color-field 56 → 93 % (3c6742b9c) · background 57 → 95 % (9bdc5a5d5) · s-text-virtual-list 58 → 70 % (1f785b6f2) · badge 59 → 75 % (271fcd4df) · cropper 60 → 94 % (f03ee5af4). Global 66 %. Los siete con diff 0 y su guard limpio.

Las cinco cosas que enseñaron, en una línea cada una (detalle abajo): el instrumento tiene TRES caras del mismo punto ciego · un guard puede CERRAR lo que mide y montar MÁS puede medir MENOS · subir un privado al contrato lo somete a reglas nuevas · lo que se escribe INLINE nunca es tema · y la mitad del trabajo que queda es ADJUDICAR, no acuñar.

Estado: EN EJECUCIÓN — F0 y F1 CERRADAS · F2 en curso, bloque F2-B CERRADO con su revisión adversarial pasada (2026-08-22); la cola sigue abierta y text-gradient ya está dentro (2026-08-22) y el 2026-08-23 entraron DIECISÉIS (tabla abajo). Alcance global 65 % (33 % al abrir el eje, 45 % tras F2-A, 50 % tras F2-B, y +6 puntos al arreglar la MEDICIÓN, no el código). Sin contrato 24; al 100 % 25; alcance <20 % 12. Últimos: chart 9 % → 89 % y result 14 % → 90 %, los dos con global a CERO tras la COSTURA. El censo cuenta 54 knobs menos: los literales ANOTADOS salen del ratio (clase exception). Y prose 1 % → 83 %.

Desde el 2026-08-23 (tarde) NO queda ningún rojo del bloque viejo — drawer (24), metrics (18), color-picker (25) y float-panel (12) medidos uno a uno y adjudicados por escrito; el guard R-5.4 pasa en todos. Los tres hallazgos reales que salieron —el velo del cajón que no pinta, las tallas del cajón que soma escribe inline y el eje size del picker que muere en el portal— están en §13 y esperan firma.

⚠ Esta frase estuvo MAL un día: se escribió el 23 por la mañana dando por cerrado el bloque viejo cuando float-panel seguía con sus doce rojos sin medir, escritos como «lo que queda» en su propio commit del 22. Lo destapó el barrido del centinela sobre TODO el ledger (REVIEW-theming-2026-08-23.md). Un componente cuyo commit dice «lo rojo que queda» NO está cerrado, y la única forma de saberlo es correr el guard sobre el ledger entero, no sobre los que tocas.

LA COLA A ESTÁ CERRADA (2026-08-24)

scroll-area 94 → 100 % · slider 97 → 100 % · tabs 96 → 97 % CERRADO · toast 98 → 100 %. Global 68 %, componentes al 100 % 45.

Los cuatro cerraron SIN ACUÑAR UNA SOLA CLAVE, y eso es el resultado, no un atajo: a los cuatro les quedaba un literal, y en los cuatro era identidad (opacity: 1 que deshace el 0 del estado oculto · el 100% de un raíl cuyo knob real es su suelo min-inline-size · el width: 100% de fitted, que ES «la lista llena el eje» · el opacity: 1 del polo en reposo de un par abierto/cerrado). Firmar un literal de identidad y cerrar es una respuesta tan válida como acuñar.

Las dos leyes que dejó

  1. ⚠⚠ ANTES DE RETIRAR UNA CLAVE, MIRA QUIÉN LA LEE — ya es el paso 8.bis de §7.3 (868bd235b). scroll-area.auto-max-block leía muerta en su propia demo y la consumen cinco recetas ajenas (combobox, command, gradient-picker, select, palabras); slider.min-block-size la fija media-player para su volumen vertical. Retirarlas habría roto seis componentes sin que un solo guard se pusiera rojo, porque ninguno mide el catálogo entero. Leer muerto en SU demo no es leer muerto en el catálogo.
  2. El indicador de un tablist es CANAL DE VALOR, no superficie de tema. La ficha de tabs proponía acuñar indicator-width/-height sobre las vars que MeasuredIndicator escribe en cada movimiento: un público encima sería un knob que un tema puede FIJAR, y fijarlo rompe el deslizamiento. Rechazado por escrito, con el precedente de navigation-menu.

⚠⚠ El hallazgo con más coste del eje: §12.9 se lleva la tarjeta de toast

Un toast risk y uno fulfill visten la misma tarjeta gris. Re-medido en la supervisión sobre la demo real con transiciones y animaciones congeladas: risk, fulfill y affirm computan idénticos (fondo, borde, franja de acento de 0,67 px y sombra). La tarjeta lleva data-intent y data-depth='overlay'; escribir --toast-risk-surface en :root no la mueve y --depth-overlay-border la repinta. La intención llega al DOM y muere en la cascada — 21 claves.

Es la primera vez que §12.9 tiene coste de PRODUCTO y no sólo de contrato, así que sube el precio de dejarla sin firmar. ⚠ Al medirlo, el tono viaja en data-intent, no en data-color: una sonda que lea data-color lo encuentra null y diagnostica «el tono no llega», que es un defecto DISTINTO.

Qué queda del eje

  • Cola A: nada. Los 16 que nunca habían pasado, cerrados o adjudicados.
  • Deuda B: tres, cada uno por su razón — audio-player (sin README de eidos donde escribir la sección), palabras y proof-of-human (carriles propios).
  • Pestañas Tokens: ninguna pendiente. Las 15 entregadas, y de propina el contador de la pestaña pasa a DERIVARSE del contrato en las 105 demos (9fc38b19a + c88b2f861): tres mentían (carousel 30/27, drag-drop 10/9, link-preview 33/35).
  • ✅ §12.9 FIRMADA Y EJECUTADA (2026-08-24) — el plano es el SUELO, :where(), especificidad cero: la receta gana donde el componente HABLA, en cualquier orden de carga, y el plano sigue pintando lo que CALLA. El frost NO baja (opt-in explícito por elemento). Doctrina en changelog.md §29, emisor en render-css.ts, guarda en active-eidos-config.test.ts. Medido: de 19 adoptantes, 16 con diff CERO sobre ~49.000 valores; los tres que se mueven son la firma haciendo su trabajo (toast recupera sus seis tonos y una franja de acento de 3 px en vez de 0,67 px de gris; tooltip sus tres variantes; popover/tooltip/link-preview el line-height que sus recetas piden — la decisión de píxel que la receta de link-preview tenía escrita como pendiente de esta firma). 17 claves salieron STALE y se retiraron del ledger.
  • Esperan tu firma: la cascada de paleta (que se lleva también el tono por defecto) · el componente compuesto contra la receta que lo compone (con la salida barata ya probada en toolbar) · el barrido de descendientes de file-upload · las cuatro claves de avatar fuera de gramática · el aria-selected literal de tags-input · el chip de reacción de chat-message.

LO QUE DEJÓ LA SEGUNDA TANDA DEL 2026-08-24 (ocho componentes)

Ocho más, por cuatro sesiones en paralelo, con la ley del índice compartido ya en el brief — y esta vez ningún commit se llevó trabajo ajeno. Global 68 %, componentes al 100 % 36 → 42.

componente antes → después lo que enseñó
chat-message 84 → 100 % el guard leía 45/75 y las TREINTA muertas eran ejes que la demo no enciende
chat-typing 89 → 100 % sus puntos son > span desnudos y la demo arranca en idle: 3 nodos de 8
fab 88 → 100 % el centinela se auto-cancela: un lift de 1234 px se lleva el nodo de debajo del cursor
image-adjustments 67 → 100 % medir de MÁS midió menos: el pase de hover recorre 30 nodos como mucho
separator 67 → 100 % sin acuñar UNA sola clave: sus dos 100% son identidad
collapsible 90 → 100 % el que ANIMA no tiene un knob de motion: su firma es emerge-expand/-collapse
toolbar 91 → 94 % CERRADO ceder el cromo al compuesto SALE GRATIS
tags-input 88 → 90 % CERRADO su tinta de etiqueta no ha pintado nunca (aria-selected literal en el morfo)

Dos techos, y los dos son doctrina: los 4 privados de tags-input son los cuatro el puente THM-2, y los 2 de toolbar son la identidad de la variante (una barra ghost ES la que no tiene relleno ni sombra).

El hallazgo que más vale de la tanda

Ceder el cromo al componente compuesto SALE GRATIS. toolbar deja que [data-button] gane —misma especificidad, emitido después— y no pierde ni una clave, porque su receta ya no escribe reglas sobre el botón (A-112). Medido ANTES de tocar nada: 51/54, y ninguna silenciosa era del botón. Es el reverso exacto de form, que conserva 23 declaraciones muertas por la misma causa. La firma pendiente de §13 («¿manda el compuesto o la receta que lo compone?») tiene por tanto una salida barata y ya probada: no escribir la regla.

Lo que la supervisión arregló

  • La sonda NO congelaba nada (10832bef4). El gate del protocolo es «diff de computed CERO» y quien lo mide leía transiciones a medias y animaciones en bucle donde pillaran. chat-typing dio 18 diffs de opacity corriendo la sonda dos veces sobre código idéntico; con la congelación, 0 sobre 1.568 valores. ⚠ El GUARD sigue congelando sólo transition: cambiarlo obliga a re-verificar el ledger entero (§13).
  • 164 fichas regeneradas, clasificadas una a una (5c8e707a0): 155 sólo la fecha, 7 mejoraban, 2 borraban prosa medida — cosidas. El generador pisa lo escrito a mano y eso ya está en §13 con su arreglo.
  • Refutado un susto: una sesión avisó de que había revertido fichas ajenas y de que cuatro veredictos §5 podían estar vacíos en HEAD. Comprobados los ocho: intactos, de 27 a 81 líneas cada uno.

Defectos reales anotados, NO corregidos (mueven píxel o tocan morfo)

  • chat-message: el chip de añadir reacción lleva data-popover-trigger y popover.css (0,2,0) le pinta fondo, borde, radio, tinta, altura, padding y tamaño de letra. Se ve en la captura: los chips de reacción son píldoras redondas y el de añadir es un cuadrado gris. Espera decisión de diseño.
  • tags-input: el morfo sella aria-selected='true' como LITERAL en cada etiqueta, así que archetypes.css (0,4,0) le fija tinta y velo de estado EN REPOSO y su token nunca ha pintado. Tiene cara de defecto de a11y (un lector anuncia todas las etiquetas como seleccionadas). Toca morfo ⇒ §12.

Cola A: CERRADA (2026-08-24)

Los cuatro, por dos sesiones en paralelo: scroll-area 94 → 100 % (3282ef978) · slider 97 → 100 % (4d3ca2fd8) · tabs 96 → 97 % CERRADO (300153f9f) · toast 98 → 100 % (b8eccc1c4). Los cuatro cerraron sin acuñar UNA sola clave: sus contratos ya estaban enteros y lo que quedaba fuera eran tres literales-identidad (un rail al 100 %, una barra al opacity: 1, la lista fitted al 100 %, la tarjeta al opacity: 1) y, en tabs, el canal de VALOR de soma. Cuando el número no se mueve porque no debe, eso ES el resultado.

Lo que dejaron los dos últimos, en una línea cada uno:

  • tabs — la mitad de un contrato puede vivir en las VARIANTES (40/79 → 70/79 barriendo data-variant y usando las dos URL que el propio demo ofrece), y su indicador es canal de valor: --indicator-{x,y,w,h} los escribe MeasuredIndicator en cada movimiento, así que un público encima sería un knob que un tema puede fijar — y fijarlo rompe el deslizamiento.
  • toast — §12.9 en una tarjeta PORTALADA se lleva la TARJETA ENTERA, 21 claves, y con coste de PRODUCTO: un toast risk y uno fulfill visten la misma tarjeta gris, y la franja de acento no se ve nunca en reposo (el plano declara el ATAJO border, que pisa el border-inline-start). Matiz para la firma: los seis {tono}-accent SÍ alcanzan bajo [data-loading], donde el @keyframes del pulso gana a toda declaración normal — la misma clave miente en reposo y dice la verdad en animación. Y una del instrumento: poner openMarker EMPEORA la medición cuando la superficie se auto-descarta (tres corridas discrepando); sin marcador el guard dispara una tarjeta nueva antes de cada token y da 51/85 tres veces seguidas.

De la deuda B siguen tres: audio-player (sin README de eidos donde escribir la sección), palabras y proof-of-human (carriles propios). Más las pestañas Tokens que queden pendientes, la deuda hermana que destapó saldar la B.

LO QUE DEJÓ LA SESIÓN DEL 2026-08-24 (doce componentes + supervisión)

Doce componentes cerrados por cinco sesiones en paralelo, cada uno con su commit aislado, y una pasada de supervisión sobre los doce (refutación estática de cada commit + centinela re-corrido + medición propia de todo claim que no fuese trivial). Cinco componentes llegan al 100 %; card cierra en 78 % porque ése ES su techo, y así está escrito en su ficha.

Los cinco hallazgos de la SUPERVISIÓN (no de la ejecución)

  1. ⚠⚠ EL ÍNDICE DE GIT ES COMPARTIDO (5be7dabf4). Hay un solo .git/index para todas las sesiones. Construir el índice desde HEAD y comprobarlo con git diff --cached protege del árbol ajeno, no de la ventana entre mirar y confirmar. Costó edc019515 (salió con tres fragmentos de rating-group y sin la mitad de los suyos), su reparación 007c2af52, y una coma de más que dejó theming-sentinel-exceptions.ts SIN PARSEAR durante cuatro commits de HEAD (88a208644). El guard no quedaba en rojo: quedaba MUERTO, y sólo se veía en checkout limpio. Regla nueva: verificación y git commit en la MISMA invocación de shell.
  2. Un árbol de trabajo rancio hace MENTIR al instrumento sin tocar un commit. El guard de card daba 0/98 —la firma del instrumento ciego— por un theming-sentinel.ts sucio con el bloque de chat-log DUPLICADO. Desde copia limpia de HEAD: 49/98, verde. Antes de creer un 0/N, comprueba que el instrumento coincide con HEAD.
  3. La cascada de paleta se lleva TAMBIÉN el tono por defecto (b7f5a7d75). El §13 decía que las {c}-primary-* salvan a la instancia sin data-color; donde soma trata el color como valor RESUELTO lo estampa siempre y neutral cae con los otros once. Medido en switch y editable.
  4. El canal inline filtrado al contrato son CUATRO claves, no dos (009af3300 + 8a4bb8e0e). Los dos de avatar parecían fuera porque su bloque de base.ts es una IIFE que el censo no supo leer hasta f68bac4a6. Re-medidos: ni el fondo del badge ni el box-shadow del anillo se mueven desde :root, y no puede existir instancia alcanzable — la puerta data-*-custom y la escritura inline viajan JUNTAS.
  5. La sonda y el guard MUEREN con --import tsx/esm (b4e4fe1fe). ReferenceError: __name is not defined dentro de page.evaluate: el ayudante keepNames de esbuild no existe en la página. Van con node a secas. El error no nombra ni a tsx ni a la sonda.

Lo que enseñó cada componente

componente antes → después lo que enseñó
chat-log 77 → 100 % el instrumento daba 15/31 y las DIECISÉIS «muertas» estaban vivas
chat-composer 79 → 100 % file-upload BARRE DESCENDIENTES y se come el token del huésped
rating-group 80 → 100 % una escala privada sube al contrato; el guard no fotografía transform
toggle 81 → 100 % el guard sólo sabía barrer UN eje: 36/126 → 70/126 sin adjudicar nada
select 83 → 92 % el portal parte el instrumento: el estado que abre TAPA los de reposo
avatar 75 → 90 % su contrato era INVISIBLE para los dos lectores (una IIFE en base.ts)
calendar 84 → 89 % una entrada de receta de FAMILIA no se juzga en la ruta de UN componente
file-upload 76 → 83 % una clave que no pintaba en NINGUNO de sus tres botones — retirada
editable 75 → 82 % el guard CERRABA la superficie que medía (sale del modo con un blur)
switch 76 → 81 % su tono NEUTRAL cae con los otros once
radio-group 77 → 81 % su punto NUNCA ha pintado su escala (el SVG escribe 1em inline)
card 78 → 78 % CERRADO el número cerrado ES el resultado: sus 8 privados son puente y conmutador

Dos verdades sobre el GATE del «diff cero»

Dos componentes NO cerraron a cero, y los dos lo declararon. Los dos son ruido del instrumento, reproducido por la supervisión sobre código idéntico:

  • calendar: 28 diffs corriendo la sonda dos veces sin tocar nada, en los dos mismos nodos (month-select / year-select) y las dos mismas propiedades. Empate de especificidad resuelto por orden de carga de Vite. Su commit además era SÓLO comentarios, así que no podía mover un píxel.
  • chat-composer: 2 diffs de blockSize (30 ↔ 36 px) en [data-…-attach], reproducidos en tres corridas del mismo código.

Un diff no cero no es automáticamente una regresión — pero SÓLO se puede declarar ruido reproduciéndolo sobre código idéntico. Los dos lo hicieron.

Lo que hay que saber antes de seguir

  • card y radio-group están CERRADOS por debajo del 100 % y no es deuda: sus privados son puente THM-2 y conmutadores por variante. Un público encima mataría el color= por instancia.

  • file-upload es HOY el único componente con un barrido de descendientes ([data-file-upload] [data-disabled], una sola ocurrencia en 162 recetas). Acotarlo mueve píxel de cualquier huésped → espera firma.

  • El guard no fotografía transform y ya son tres los componentes que lo sufren (background, rating-group, card). Arreglarlo obliga a re-verificar el ledger entero, así que se adjudica de uno en uno.

  • La demo de chat-log se TOCÓ como instrumento (montaba tres de sus seis partes sólo en la prosa, así que once claves no tenían nodo que pintar). Roza la regla de que las demos quedan fuera del eje: queda dicho, no escondido.

  • LA COLA, con la separación que la primera redacción se dejó (corregido 2026-08-24 al revisar: decía once y son treinta y cuatro, contados contra el censo y el registro §8).

    A. Nunca han pasado por el eje — 16. Sin entrada en §8 y sin README: field-langs 0 % (bloqueado por el mandato Field) · chronos 1 % (carril WIP) · mockup 38 % (⚠ sin ruta de demo) · sidebar 55 % (eje propio) · image-adjustments 67 % · separator 67 % · chat-message 84 % · fab 88 % · tags-input 88 % · chat-typing 89 % · collapsible 90 % · toolbar 91 % · scroll-area 94 % · tabs 96 % · slider 97 % · toast 98 %.

    B. Pasaron por el eje pero les FALTABA el README «Talla y tema» — 18, quedan 3. ✅ Quince saldados el 2026-08-24 (a6df34c05), con la tabla DERIVADA del contrato y del ledger. Quedan audio-player (no tiene README de eidos: no hay dónde escribir la sección), palabras y proof-of-human (carriles propios). ⚠ Y sale una deuda hermana: ninguno de los quince tiene pestaña Tokens en su demo — el paso 11 del protocolo llegó después de que cerraran, así que hasta que la tengan la tabla del README es la fuente, y así está escrito en los quince. La lista original era: Su número está adjudicado y su commit hecho; lo que falta es el artefacto del paso §7 que se volvió obligatorio después de que cerraran (e522e04e6 recuperó 21, éstos quedaron fuera): proof-of-human · palabras · audio-player · drawer · radio-cards · password-field · tag-group · popover · color-picker · timeline · stepper · aura · metrics · float-panel · chart · accordion · gradient-builder · nav-tree. No es cola de tokenización: es deuda de documentación, y se salda con un barrido derivado del contrato como el que ya se hizo una vez.

    Regenera siempre esta lista del censo, nunca de memoria: el criterio es «alcance < 100 % y sin sección ## Talla y tema», y el desempate entre A y B es si tiene entrada en el registro §8.

LO QUE DEJÓ LA SESIÓN DEL 2026-08-23

Dieciséis componentes (11 cosidos + 6 de cola + 2 adjudicados sin tocar código), tres pasadas de rojos anteriores (drawer 24, metrics 18, color-picker 25) y una corrección a la baja: context-menu 90 → 87 %, al retirarle un token que mentía.

componente antes → después lo que enseñó
menubar 15 → 88 % el plano overlay GANA donde el componente habla
field 73 → 98 % sus 18 rojos estaban vivos; ::placeholder sin placeholder
onion-menu 29 → 84 % :global() en CSS plano ⇒ regla DESCARTADA
button 55 → 91 % la cascada de paleta anula 419 claves del catálogo
callout 27 → 79 % la base óptica deja de copiar las métricas del sistema
empty-state 29 → 100 % 26/26 sin una sola adjudicación
dropdown-menu 57 → 96 % y la tipografía del gemelo, RETIRADA
checkbox 59 → 72 % un huérfano que el test de huérfanos no puede ver
image 60 → 96 % la página que nunca alcanza networkidle
anchor-nav 20 → 100 % el censo no veía los bordes POR LADO
banner 30 → 100 % 4/42 → 53/53 al estampar data-intent y barrer variantes
date-range-field 29 → 100 % una clave pública que giraba OTRA cosa
time-range-field 29 → 100 % el gemelo con el MISMO defecto copiado
tooltip 52 → 75 % el plano le anula las TRES variantes
audio-player 37 → 56 % el instrumento medía CERO nodos
cola pequeña 6 comp. section TAPABA sus propias claves públicas

Las cinco leyes que salieron, en una línea cada una:

  1. El portal es una frontera de cascada — un privado de la raíz no la cruza (mató el eje size del color-picker).
  2. El plano overlay gana donde el componente habla — y no sólo la tipografía: en tooltip se queda el fondo, el borde y la sombra, y con ellos las tres variantes.
  3. La cascada de paleta anula los tonos del componente — el bloque genérico va el ÚLTIMO y gana por orden: 419 claves en 49 recetas.
  4. Un respaldo escrito a mano envejece contra su token — , 1.4 contra un --font-line-height-sm que vale 1.45, en dos componentes.
  5. Donde hay un par, el defecto viene en par — los dos range-field traían el mismo nombre equivocado, copiado al duplicar.

Lo que el instrumento aprendió (todo en scripts/, ya commiteado):

  • openMarker — no pulsar lo que YA está abierto (onion-menu nace abierto y el guard lo cerraba: 12 de 20 tokens muertos).
  • sweepAttr — barrer una variante mientras se mide (banner, tooltip, button).
  • El barrido de tono estampa data-color y data-intent (banner).
  • extraNodes para hooks de CLASE (onion-menu) y para partes de OTRO chasis (audio-player: 0 → 48 nodos).
  • Caída a load cuando networkidle no llega nunca (image).
  • urls para un componente sin ruta (surface, audio-player).
  • Excepciones por PATRÓN (SENTINEL_PATTERN_EXCEPTIONS) para una razón arquitectónica que afecta a 56 claves de golpe.
  • El censo: literal ANOTADO dentro de un privado = ausencia firmada; y los ocho longhands de borde POR LADO (+30 filas en el catálogo).

⚠ Sonda y centinela filtran por data-{c}-*: un componente que estiliza HTML crudo (prose) o cuyas partes no llevan atributo se mide sobre UN nodo y el gate no significa nada. Los dos aceptan ya un selector extra (EXTRA_NODES en la sonda, extraNodes en COMPONENT_OVERRIDES).

LA REVISIÓN DE LOS 26 (2026-08-23) — lo primero que hay que saber

Todo lo ejecutado desde la revisión del 22 pasó por fin su análisis: REVIEW-theming-2026-08-23.md. 33 componentes, 5 errores reales, 5 desviaciones, y la §7.7 con 5 hipótesis y 5 refutaciones. Arreglado todo (233ef989b · bfe7727b0 · e522e04e6 · 2f243e1c0). Las cuatro lecciones que cambian cómo se trabaja:

  1. Corre el guard sobre el ledger ENTERO, no sobre lo que tocas. Así salió que float-panel llevaba DOCE rojos sin adjudicar desde el 22 —escritos como «lo rojo que queda» en su propio commit— mientras el handoff daba el bloque viejo por cerrado. Un componente cuyo commit dice «lo que queda» NO está cerrado.
  2. El nombre de un token no es el de la propiedad. badge acuñó min-block-size-{k} donde la ranura es height: 24 componentes dimensionan por …-height-{k} y era el único. R-5.3 no guarda el vocabulario DIMENSIONAL (§13) — mientras no lo haga, cuenta el nombre en el catálogo antes de acuñarlo, también para las dimensiones.
  3. Los pasos 8 y 9 del protocolo (README «Talla y tema» + pestaña Tokens) se saltaron en 22 componentes de una tanda entera y nadie lo notó hasta revisar los git show --stat. Si no está en el commit, no se hizo.
  4. Antes de comparar contra una base, comprueba que la base MONTA. El worktree necesita server: { fs: { allow: [...] } } cuando su node_modules es una junction; sin eso Vite responde 403, la página no hidrata y el diff sale limpio contra la nada.

LO QUE DEJÓ LA TANDA DE SIETE (2026-08-23) — léelo antes de seguir

El instrumento tiene TRES caras del mismo punto ciego, y ninguna se detecta sola. El filtro es data-{c}-*, así que no ve: (1) HTML crudo estilado por descendencia (prose), (2) partes que compone el CONSUMIDOR — los <a> desnudos de un mega-menú (navigation-menu: 10 nodos, ninguno era el panel), y (3) partes de un componente COMPUESTO — el playhead de waveform ES el thumb de un Slider embebido (4 nodos, ninguno era él). Añade una cuarta forma: las capas OPT-IN (background medía 2 de 37 tokens; badge, 2 nodos de 5 porque dot / icon / remove nacen apagados). Cuenta los nodos SIEMPRE, y si el número es pequeño, sospecha antes de creerte el gate.

Un guard puede cerrar lo que mide, y montar más puede medir menos. navigation-menu abre por pointerenter, y el aparcado del puntero en (0,0) que el guard hace tras el clic es exactamente el gesto que lo cierra: daba no-determinismo y las dos caras del error a la vez (falso negativo en las filas, falso POSITIVO en el token que ensancha el panel). Y en background, encender el foco puntual y el paralaje bajó la corrida de 17 a 9 tokens, porque los dos repintan la capa donde se miden los patrones. Nace prepareWith (enciende TODOS los controles que se le den, al contrario que openWith), y con él la regla: enciende sólo lo que no tapa, y compruébalo corriendo el guard antes y después de cada control.

Subir un privado al contrato lo somete a reglas que en el CSS no se le aplicaban. Dos veces la misma tarde: badge.min-block-size-* y cropper.zoom-btn-font-size copiaban el primitivo CRUDO que el CSS usaba, y recipe-css-contract exige la coordenada del BUNDLE. La suite lo caza en el acto — pero sólo DESPUÉS de escribirlo, así que escribe el bundle desde el principio.

Lo que el componente escribe INLINE no es tema, nunca. s-text-virtual-list se queda en 70 % porque su triple tipográfico lo escribe él en cada render desde props resueltas (y tiene que ser exacto: el canvas que mide el texto lee getComputedStyle). Igual el --_background-gradient-image y el rect del indicador de nav-menu. Un público encima MIENTE.

La mitad del trabajo que queda es adjudicar, no acuñar. color-field tenía 13 tokens que nunca se habían medido y background 19; los dos con contrato sano. Un componente sin veredicto §5 no es un componente sin tokens: es uno sin MEDIR. Y hay dos atajos legítimos: la excepción POR PATRÓN cuando la razón es arquitectónica (las 40 claves de tono de badge, la cascada de paleta) y la clase «sólo el paso en vigor pinta» para toda escala resuelta por atributo.

Dos trampas de medición que costaron tiempo:

  • Congela transition en TODA medición a mano. Una propiedad transicionada devuelve el valor VIEJO si la lees en el acto — pasó con color en navigation-menu, y ya había pasado con el cuentagotas del color-picker.
  • Mide el nodo que PINTA, no el primero que casa el selector. En color-field sospeché del @media (prefers-reduced-motion) durante tres diagnósticos: la causa era que el primer [data-color-field-segment] es un literal, que declara transition: none de su propia cosecha.

LO QUE DEJÓ navigation-menu (2026-08-23) — el detalle

Tres cosas que se repetirán en la cola, y cuestan una medición cada una:

  1. Cuenta los nodos ANTES de creerte el gate — otra vez, y por una causa nueva. Las filas de su mega-menú son los <a> desnudos del consumidor, que la receta estiliza por DESCENDENCIA ([data-navigation-menu-content] :is(a, …)). Sonda y centinela filtran por data-{c}-*: veían 10 nodos y ninguno era el panel ni una fila — 9 de sus 19 knobs quedaban fuera del diff. Es la clase de prose y onion-menu, pero por composición del consumidor. Los dos instrumentos aceptan ya el selector extra para este componente, y el paso open de la sonda —que NO honraba EXTRA_NODES— ahora sí.
  2. Un panel de HOVER lo cierra el propio guard. Abre con pointerenter y pointerleave PROGRAMA el cierre; el guard abre con clic y luego aparca el puntero en (0,0), que es justo el gesto que lo cierra. Daba no-determinismo (dos corridas del mismo código discrepando) y las dos caras del error a la vez: falso negativo en los tokens de la fila y falso POSITIVO en content-link-padding-inline, que leía «vivo» porque ensancha el panel que sí se medía. Se arregla con openBy: 'hover'. Regla: antes de creer un «no effect», comprueba que la superficie seguía abierta.
  3. color puede estar EN TRANSICIÓN. Forzar data-disabled y leer en el acto devuelve el valor VIEJO: tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. Es la trampa del cuentagotas del color-picker, en otro componente y sobre otra propiedad. Toda medición a mano congela transition — el guard ya lo hace, tu script de un solo uso no.

Y dos de doctrina:

  • El catálogo decide el nombre, no la §4. Aquí falló de CINCO maneras (las cuatro conocidas más una nueva: un aviso de capa compartida disparado por homonimia — menu-indicator es el check de los CheckboxItem, no el subrayado de esta barra). Contar en base.ts resolvió los 19 nombres.
  • Un nombre de parte que existe en OTRO sitio del mismo componente miente. Los gemelos llaman item-* a la fila del panel; aquí item es el <li> de la barra y el Link de barra viste el cromo del trigger, así que ni item-* ni link-* valen: content-link-* nombra el nodo por donde vive.

Contabilidad: el censo decide el eje size grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques — así que todo componente que aplique bien el protocolo aparece «sin eje size». Ya le pasa a sidebar, nav-tree y ahora a navigation-menu. Registrado en §13; no es deuda.

LO PRIMERO AL ENTRAR (cinco minutos, en este orden)

  1. Comprueba que el suelo no se ha movido:

    node --import tsx/esm scripts/theming-census.ts        # 162 · 5.137 · 2.770 (60 %) · 24 sin contrato
    node --import tsx/esm scripts/theming-census.ts --names # DESVIADAS 0 — la gramática se mantiene
    

    El segundo cubre componentes que component:audit NO ve (p. ej. picker-shell, eidos-only sin morfo): córrelo siempre, no te fíes del audit para la gramática.

  2. Arranca el dev server ANTES de medir nada: preview_start con la config dev de .claude/launch.json (puerto 5180). Las sondas se ejecutan con node, no con tsx, desde la raíz, y su default es 5173: pásales siempre la URL como tercer argumento.

  3. Lee el veredicto §5 de la ficha (docs/audit/theming/{c}.md). Si está vacío — lo están ~130 de 171 — lo escribes TÚ midiendo, y luego ejecutas: ése fue el flujo de todo F2-B. La §4 generada no se sigue a ciegas; ver «Lo que F2-B enseñó».

QUÉ HACER AHORA

1. F2-B está CERRADO — su revisión adversarial (§7.7) se hizo el 2026-08-22

4 hipótesis · 3 refutadas · 1 hallazgo real. Detalle en PLAN §8. Lo que hay que recordar de ella:

  • drag-drop.preview-z mentió y se retiró: soma escribe su z-index INLINE, y la adjudicación que lo daba por vivo se había medido sobre un nodo creado a mano. Una adjudicación «forzado → alcanza» vale lo que valga el nodo que se forzó — si lo creaste tú, no lleva lo que soma le pone.
  • El orden de carga NO es el mismo en dev que en producción (medido con build + CDP). El empate de §12.9 sigue abierto, ahora con las dos medidas.
  • Un worktree cuyo node_modules es una junction fuera del root no hidrata: Vite responde 403 a @fs/…. La primera pasada comparó contra una base muerta sin que nada lo delatara — añade server: { fs: { allow: [...] } } a su vite.config.ts y comprueba que un componente monta antes de comparar.

2. El censo cuenta distinto desde el 2026-08-22 — léelo antes de comparar cifras

Un privado que DERIVA de un público ya cuenta como alcanzable. Es lo que F0 encargaba («leer --_c-x: var(--c-y) y reclasificar») y sólo se había implementado para el INFORME: la columna «¿deriva de un público?» de la §3 lo decía, pero el contador lo seguía puntuando como deuda. Medido: 240 privados en 59 componentes estaban en ese estado, haciendo exactamente lo que la doctrina prescribe y penalizados por ello.

El cierre es TRANSITIVO (--_a: calc(var(--_b) * .3) alcanza si --_b alcanza) y tiene muta-prueba: apuntar un privado a un primitivo crudo baja la cifra, revertirlo la sube. Global 50 % → 56 %, privados 625 → 341, al 100 % 10 → 14. avatar pasó de 36 % a 75 % y picker-shell de 76 % a 95 % sin tocar una línea de su código.

Consecuencia: cualquier cifra anterior al 2026-08-22 no es comparable con las de ahora. Las de este handoff ya son las nuevas.

3. Lo que dejó la sesión del 22 (24 componentes en F2-B, todos con 0 diffs)

grid-list 85 · textarea 80 · tree-view 92 · code-block 81 · card-group 90 · spinner 70 · picker-shell 95 · link-preview 100 · drag-drop 89 · kbd 86 · link 36 · code 100 · virtual-list 67 · virtual-grid 73 · skeleton 45 · label 100 · announce 88 · clipboard 63 · scroll-frames 33 · mark 60 · menu-dial 38 · knob 75 · skin-media-player 22 · text-circular 100 · text-focus 100. Más cinco adjudicados como 0 % estructural (aspect-ratio, text-blur, cascade, motion, date-picker), la revisión adversarial §7.7, el arreglo del censo y el test del suelo.

Tres cosas que enseñó y conviene tener delante al seguir:

  1. La propuesta §4 generada falla de cuatro maneras. Hereda el incumplimiento del CSS de partida (si ya violaba recipe-css-contract, lo repite); infla hasta 4× (71 tokens para 23 knobs en spinner); nombra por el primer selector de la regla y no por el nodo pintado (llamaba root-bg a una guía de indentación); y usa el slot color donde la gramática firmada exige fg — R-5.3 lo caza, pero después de escribirlo.
  2. Declarar un público «de facto» puede MATAR una escala. Varios componentes consumen var(--c-x, <default>) sin declarar --c-x. Si detrás del fallback hay un privado por talla (knob), declarar el público lo mata: el público va primero y gana siempre. Hay que subir la escala al TSC. Si no lo hay (text-circular, text-focus), declarar es seguro.
  3. Cuenta los nodos que ve la sonda antes de creerte un gate verde. Cuatro componentes miden UN nodo, picker-shell medía CERO. Y el centinela tiene tres puntos ciegos ya medidos: congela transiciones, su valor grande se capa contra min(), y no ve lo que la demo no monta.

3.bis Lo último ejecutado — text-gradient (2026-08-22)

67 % → 100 %, 7 claves. Detalle y porqués en PLAN §8 y en el veredicto §5 de su ficha. Lo que conviene tener delante al seguir con la cola:

  • La ficha §4 falla también contra el CATÁLOGO, no sólo contra la doctrina. Proponía conservar weight (77 claves del catálogo dicen font-weight), y marcaba width/height con «⚠ eje físico» cuando el calc(100% - 2px) era UN knob —el trazo del marco— del que el hueco deriva. Antes de acuñar un nombre, cuéntalo en base.ts: el catálogo es el que habla.
  • Un literal igual a un paso de escala no se congela: se nombra. 1px → var(--border-width), blur(4px) → var(--blur-sm). Y ojo con blur: es familia MÉTRICA del eje scaling (theming §23), así que un literal es ciego al zoom global aunque el computed coincida a scaling 100.
  • Los dos instrumentos ya saben encender una VARIANTE. La sonda tiene DEMO_VARIANTS (chips que se re-pulsan tras CADA recarga — la sonda recarga una vez por talla) y el centinela acepta openWith apuntando a un chip, no sólo a un trigger de panel. Sin eso, esta demo daba 2 nodos y los tres tokens del marco no se medían. Cuenta los nodos antes de creerte el gate.
  • Las demos NO forman parte del eje (autor, 2026-08-22): sólo los componentes. Un hallazgo de demo o del harness del sitio no se registra ni se mide; la demo se toca únicamente como INSTRUMENTO (encender una variante para que la sonda vea los nodos), y lo mínimo.

3.ter text-scramble ADJUDICADO (2026-08-22) — sin contrato, y por qué

Su 50 % no es deuda ni logro: dos literales son la técnica sr-only (como text-blur) y los dos «públicos» son tipografía que no debería acuñar, o sea la firma 1.bis. Escribirle un contrato ahora sería acuñar justo lo que esa firma propone retirar. Veredicto §5 escrito, cero cambios de código.

De paso salió el barrido mecánico que --radius-xs y --font-size-4xl pedían: 9 referencias fantasma con fallback en el catálogo (5.793 nombres declarados). Tabla en next-features §13. La más grave no es de esta familia: --color-content-tertiary sigue vivo en TRES recetas —es el caso histórico que recipe-contract §1 cita como origen de la regla— y los cuatro --style-*-font-variation-settings de text.css significan que el eje de fuente variable no llega nunca al primitivo Text. Ninguna se arregla aquí.

3.ante LA VÁLVULA DEL CANON ya se lee (2026-08-22)

recipe-contract §3: un literal con /* literal: <razón> */ en su propia declaración es una desviación FIRMADA. component-audit la honra desde siempre; el censo no, porque strip() blanquea los comentarios antes de clasificar — 81 anotaciones contaban como deuda. Ahora caen en la clase exception, fuera del ratio como system: 54 knobs en 12 componentes (prose 21 · proof-of-human 16 · palabras 3 · timeline 2 · result 1 …). Global 58 % → 59 %.

Consecuencia práctica al tokenizar: antes de acuñar un literal, mira si ya lleva su anotación — si la lleva, el canon ya decidió que ahí no va un token, y la ficha lo lista ahora en su §1.4 con la razón escrita. Y si vas a dejar un literal fuera, ANÓTALO: es la única forma de que no vuelva a contarse como deuda.

3.pre EL PUENTE DE PALETA no es deuda (2026-08-22)

De las 687 declaraciones del catálogo que leen un privado, 130 leen un --_{c}-palette-*: el puente THM-2 que la capa compartida alimenta por instancia desde [data-color]. No se acuña: un público encima dejaría que un tema lo fijara y matara en silencio el color= de cada instancia. Domina la columna private de todo componente con paleta, así que el techo de ésos no es el 100 %. Tres ya adjudicados (tag-group 65 %, stepper 79 %, timeline 73 %, éste además con el CONMUTADOR por data-intent); los demás heredan la razón: radio-group 10, checkbox 8, file-upload 8, calendar / editable / los tres grids 7 — pero ojo, esos tienen ADEMÁS global y sí les toca trabajo real.

3.quater LA COSTURA (2026-08-22) — lo más importante de la sesión

El valor es del sistema; el knob es del componente. Tokenizando chart y result di por doctrina que «el texto no recibe tokens del componente» porque su valor viene del canon, y dejé el CSS leyendo var(--style-caption-font-size) y var(--color-content-*) A PELO. Es media doctrina: PLAN §2-A pide que TODO knob de apariencia lea --{c}-{slot}. Sin la costura, un tema no puede aclarar las anotaciones de un gráfico denso sin mover caption en toda la app, y getRecipeTokens() / /temas no ven esa superficie. El molde ya estaba escrito: --accordion-trigger-font-family: var(--style-label-font-family) (§Vertebración tipográfica), y D-TH.2 aclara que sólo los seis PRIMITIVOS quedan fuera. Resultado: chart 49 % → 89 %, result 23 % → 86 %, y global a CERO en los dos.

Dos formas, y se eligen midiendo: por ROL cuando un rol se repite sobre muchos nodos (chart: 2 roles × 3 ejes = 6 claves para 19 nodos de texto; por parte habrían sido ~30 con dos valores) y por PARTE cuando cada parte tiene tipografía propia y ningún rol la cubre (result: 15). Agrupa las filas por valor ANTES de decidir.

Y una nota de §13 retirada por equivocada: escribí que el censo penalizaba consumir la capa tipográfica. El censo medía bien; faltaba la costura. Cuando la métrica y la doctrina discrepan, sospecha de la lectura antes que del instrumento. listbox (68 %, ritmo de fila prestado de list-surface) es la misma pregunta un piso más arriba: pendiente de revisar con este molde.

3.quinquies chart — el instrumento, tres veces ciego

Lo que hay que llevarse de aquí:

  • La ficha pedía 71 claves; ~20 habrían llevado el mismo valor. Antes de aceptar una propuesta larga, agrupa las filas por VALOR: las 84 de chart colapsaban a 26. Nombrar por parte lo que es un concepto es la inflación de spinner con otra cara.
  • El texto de un componente no recibe tokens propios: rol de tamaño de los estilos con nombre (caption / label), tinta y peso de las ranuras del sistema. Sólo se acuña la tinta de lo que NO es texto.
  • El guard ya sabe medir en VARIAS rutas (urls en COMPONENT_OVERRIDES): un token vive si alguna superficie del componente lo sigue. Chart tiene 16; picker-shell ya lleva la del date-picker (su ruta propia es 404).
  • El guard ya lee SVG (fill, stroke, strokeWidth, fillOpacity, r, rx…) y elige el valor centinela por el FINAL del nombre, no por subcadena — slice-stroke-width recibía un color por contener «stroke».
  • Tras cualquier cambio del guard: re-verificar los 28 componentes con ledger (se hizo: cero regresiones, cero STALE). Y ojo, correr doce a la vez contra el mismo dev server da falsos exit=1 por timeout: repite el que falle a solas antes de creerte una regresión.

4. La cola que queda, por alcance

componente knobs nota
palabras 471 (22 %) el mayor del catálogo con diferencia, sin tocar; D-TH.3 decide si entra
field-langs 41 espera el mandato Field (firma 2), que NO es tokenización: manda que los x-field incorporen el wrapper Field — cambia composición y mueve píxel
mockup 29 (38 %) ⚠ sin ruta de demo (404), como picker-shell: sin sonda ni guard. Y sale NEEDS-WORK en el audit, preexistente
chronos 245 (1 %) el segundo mayor; sin abrir
proof-of-human 95 (15 %) escena de tono fijo firmada; lo demás son hooks de clase (eje aparte)
prose 74 (1 %) su superficie es la capa tipográfica: mismo caso que chart, un piso más arriba
nav-tree · banner · … varios ver el censo, reach < 20 % = 12

Cerrados el 2026-08-23 (siete): navigation-menu 100 % · background 95 % · cropper 94 % · color-field 93 % · badge 75 % · s-text-virtual-list 70 % · waveform 86 %. El siguiente por alcance entre los medibles y sin bloqueo es emoji-picker (61 %), y luego meter / spin-field (64 %), pin-input (65 %), dialog (67 %, ⚠ overlay: no acuñes tipografía hasta §12.9), progress (69 %), color-swatch (70 %), form (71 %)… El mayor no bloqueado sigue siendo chronos (1 %, carril WIP de D-TH.3, y «tiempo» es el ÚLTIMO de D-TH.4).

avatar ya está al 75 % y knob al 75 % (ambos hechos). heading / text / display son primitivos tipográficos: su superficie es --style-* y el censo los saca del ratio. range-calendar / month-grid / year-grid están a 0 % honesto (consumen calendar-surface).

heading / text / display son primitivos tipográficos: su superficie es --style-* y el censo ya los saca del ratio. range-calendar / month-grid / year-grid están a 0 % honesto (consumen calendar-surface). alert-dialog, css-field, mask-field, number-field, highlight, stack, wrap, auto-grid tienen cero knobs: no hay nada que tokenizar.

5. Lo que espera TU FIRMA (todo medido, nada bloquea la cola)

  1. El plano overlay impone tipografía a VEINTE componentes — y en tooltip se queda además el FONDO, el BORDE y la SOMBRA, con lo que sus tres variantes computan lo mismo (medido 2026-08-23: solid, outline y ghost idénticas). Ya no es una pregunta sobre tipografía: es sobre qué manda, el plano o la receta. Cuatro componentes han tenido que RETIRAR o ADJUDICAR claves por esto (menubar, dropdown-menu, context-menu, tooltip; combobox sigue con la suya, registrada). (font-family + line-height junto a superficie/borde/sombra). ¿Un plano de profundidad manda sobre la tipografía o sólo sobre la superficie? — §12.9. Es la que más urge: cada overlay que se tokenice la arrastra. Y ya tiene MECANISMO (menubar, 2026-08-23): el plano no sólo impone donde el componente calla — GANA donde el componente habla. Declara font-family y line-height con la misma especificidad (0,1,0) y más tarde en la cascada, así que dos claves acuñadas sobre declaraciones que la receta YA tenía salieron muertas en el centinela, mientras color y font-size del mismo bloque alcanzaban. Traducción operativa: hasta que firmes, no acuñes font-family ni line-height en ninguna superficie overlay — miente. 1.bis La familia text-effects ACUÑA tipografía propia contra el canon (autor, 2026-08-22: «¿qué sentido tiene que text-effects introduzca diferentes nombres de tipografía cuando ya hay un canon establecido?»). Siete claves en CUATRO de los cinco (medido al adjudicar text-scramble): text-focus.word-size (3rem, vía el fantasma --font-size-4xl) y word-weight (900) · text-circular.weight (900) y font-size (1.5rem) · text-gradient.font-weight (500, subida el 22) · text-scramble.font y .size (sin declarar). La de text-scramble apunta además a --font-mono, que NO existe: pinta ui-monospace e ignora la mono del tema. text-blur es el único limpio: hereda. Son opiniones del seed react-bits convertidas en contrato por «valor verbatim». Doctrina F2-B regla 2: una receta CONSUME la capa tipográfica (--style-*) o HEREDA (link → inherit); un tratamiento de pintura sobre texto real no tiene tipografía propia (un TextGradient dentro de un h1 ES un h1). Resolución propuesta: retirar las cinco claves y heredar; mueve defaults (TextFocus deja de imponer 3rem/900, TextGradient deja de forzar 500) → firma. Con ello muere el fantasma 4xl. El módulo de tipografía NO se toca. Registrado en next-features §13.
  2. El harness del sitio tapa a code-block y code: [data-uix-docs] pre pisa SIETE propiedades del <pre>, y [data-uix-docs] code el radio del inline. Acotarlo toca el CSS que sirve a ~162 páginas — §13.
  3. El censo no distingue «0 % por deuda» de «0 % POR NATURALEZA» (cinco componentes ya). Sin una clase structural, el gate de F3 es inalcanzable por construcción — §13.
  4. El velo derrama sobre el subárbol entero en tree-view (archetype: 'item' en el <li> branch: 336 px velados en vez de 36) — §12.8, morfo.
  5. La selección de fila de grid-list nunca ha pintado (soma estampa data-state, no data-selected) — §13, morfo.
  6. El borde de foco de textarea sólo se ve con Tab: hover (0,4,0) > foco (0,3,0) > invalid (0,2,0) — §13.
  7. CUATRO canónicos sin demo propia (404): picker-shell, mockup, surface y audio-player. Sin ruta no hay sonda ni guard, y el guard miente en verde-rojo: sobre una página vacía reportó 0/25 para surface, incluido un radius vivísimo. Los tres medibles se apuntaron a la ruta donde SÍ se renderizan; mockup sigue sin poder medirse — §13.
  8. Extender la clase system del censo a las capas compartidas (viene de antes de esta sesión).
  9. Dos reglas que el navegador DESCARTA: onion-menu.css y timeline.css escriben :global(svg) en un CSS PLANO, donde el selector es inválido. El glifo del onion computa 14px en vez del 70 % que la regla cree pintar. Arreglarlo mueve píxel en los dos — §13.
  10. ⚠⚠ LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE — 419 claves públicas (medido 2026-08-23 en button, badge y callout). El forward emite un bloque por tono y, AL FINAL, uno genérico [data-{c}][data-color] que resuelve desde el --palette-* global; misma especificidad (0,2,0), gana el último. --{c}-risk-solid no mueve nada; --palette-solid y --color-risk-solid en el nodo sí. 49 recetas emiten ese bloque. El arreglo cabe en el ORDEN DE EMISIÓN y no movería un píxel, pero cambia el contrato de la jaula del color → §13. Es la más gorda del eje.
  11. El eje size del color-picker muere en el portal (2026-08-23): la anchura del panel está cableada al paso md (--_color-picker-content-width se declara tres veces y no lo consume nadie) y el tamaño de las muestras DENTRO del panel también. Un picker sm y uno lg abren el mismo panel de 344 px. El arreglo es una escala por talla en el ámbito del CONTENIDO —que sí lleva su data-size—; mueve píxel en sm y lg → §13. Ley general: el portal es una frontera de cascada; un privado declarado en la raíz no la cruza.

EL PROTOCOLO, EN CORTO (el largo está en PLAN-theming.md §7)

Un componente = un commit. Por componente:

  1. Leer entero: receta, su bloque en recipes/base.ts, README de eidos, morfo, y el veredicto §5 de su ficha.
  2. Sonda ANTES (__theming-probe.ts {c} antes.json <url>).
  3. Escribir el contrato en recipes/base.ts — fusionando en el bloque existente si ya lo hay (los forwards de paleta THM-2 ya ocupan bloque en muchos componentes; un segundo bloque con el mismo nombre se descarta EN SILENCIO).
  4. La receta consume los nombres resueltos; retirar los bloques [data-size] del CSS — los emite el TSC, y dejarlos vivos hace que el diff dé 0 por la ruta vieja.
  5. npm run generate:eidos-css, sonda DESPUÉS, diff = 0.
  6. Guard R-5.4 (npm run theming:sentinel -- {c} <url>) y adjudicar cada «no effect» uno a uno EN EL LEDGER (scripts/theming-sentinel-exceptions.ts, una razón medida por token) — el guard falla con un muerto sin adjudicar, y avisa de excepciones STALE.
  7. Guards: censo --only {c} · component-audit --only {c} · eidos-lint {c} · vitest run src/uix/eidos (comparar POR FICHERO: el rojo conocido es skin-media-player) · rtl:check · docs:check · npm run check atribuido POR FICHERO (73 errores globales son de otras sesiones).
  8. README del componente con su sección «Talla y tema».
  9. Tab Tokens en la demo — una línea: <TokensPanel component="x" stage={stageRef ?? undefined} /> + su botón.
  10. Commit con las cifras y los artefactos en el mensaje.

LO QUE F2-B ENSEÑÓ (2026-08-22 — léelo antes de tocar nada)

Un alcance bajo puede ser doctrina bien aplicada, no deuda. menu-dial 25 % porque sus ejes de colocación son de la capa viewport-placement y la receta escribe en sus RANURAS DE OVERRIDE; link 36 % porque sus escotillas caen en inherit, que es semántico (el enlace toma la tipografía del párrafo); scroll-frames 33 % porque es layout puro. El % no es la métrica de calidad — lo que importa es que lo que queda fuera esté MEDIDO y escrito.

Cuatro clases que se repiten, con su veredicto ya fijado:

  1. Un privado con DOS fuentes es un conmutador, no deuda. Cinco casos (textarea, spinner, skeleton, code, label): aplanarlo obligaría a duplicar cada regla por color. Se queda privado y se adjudica.
  2. Lo que la capa tipográfica posee NO se acuña (--style-*): cinco casos (code-block, kbd, announce, code, label).
  3. Un forward de paleta THM-2 leído a pelo no admite público sin duplicar la regla.
  4. Identidad ≠ knob: 100%, 50% de un círculo, line-height: 1 de una tecla, transparent de una variante ghost.

La propuesta §4 generada FALLA de tres maneras distintas, y las tres costaron medición:

  • Hereda el incumplimiento del CSS de partida: copia el valor verbatim, así que si ya violaba recipe-css-contract (primitivo crudo en vez del bundle), la propuesta lo repite. Lo cazó la suite en code-block.
  • Infla hasta 4×: en spinner pedía 71 tokens para 23 knobs, multiplicando UNA coordenada de talla por variante × parte × dimensión — veinte claves con el mismo valor.
  • Nombra por el primer selector de la regla, no por el nodo pintado: en tree-view llamaba root-width / root-bg a la GUÍA de indentación, y fundía en un solo nombre el padding-inline de la fila y el paso de profundidad, que se SUMAN en el mismo calc.

El instrumento estándar no ve tres clases de componente, y una sonda que compara pocos nodos pasa en falso: panel portalado que abre en hover (link-preview: 1 nodo), componente sin demo propia y con partes de nombre genérico (picker-shell: 0 nodos), y parte que sólo existe durante una interacción (el fantasma de drag-drop, el indicador de clipboard, el rótulo de scroll-frames). Cuenta siempre los nodos que ve la sonda antes de creerte un gate en verde.

El guard R-5.4 ganó SEIS capacidades en este bloque, todas nacidas de una medición y con los del ledger re-verificados sin regresión tras cada una: puntero parqueado tras el blur (reopen() corre antes de CADA token), lectura de ::placeholder (la nota de §13 que decía que no se podía era falsa), COMPONENT_OVERRIDES de prefijo de atributo y selector de apertura, apertura por hover, y text-decoration-thickness / -underline-offset en la lista de propiedades.

Y dos errores de proceso que costaron tiempo:

  • El guard de claves duplicadas del handoff inspeccionaba el VACÍO: grep -E lee como una t literal — 0 coincidencias sobre 130 bloques. Corregido más abajo con una versión que CUENTA.
  • $? tras una tubería es el del último comando, no el del guard: un fichero de excepciones con sintaxis inválida pasó por bueno. Comprueba el exit REAL.

LO QUE F2-A ENSEÑÓ (léelo antes de tocar nada)

Sobre los veredictos. Tres de ocho tenían un error de lectura que sólo apareció midiendo: el título de feed escala por aria-level, no por talla; --gp-current-gradient lo estampa soma, no el wrapper de eidos; y el trigger de gradient-picker no había que tokenizarlo sino borrarlo. El veredicto orienta; la medición decide.

Un token que no mueve nada es un token que miente. Ocurrió tres veces: table.selected-row-fg y la tinta de listbox (el arquetipo las gana por especificidad) y el cromo ENTERO del trigger de gradient-picker (36 de 45 tokens sin efecto — lo pinta popover.css, que gana 0,2,0 contra 0,1,0). En los tres casos la acción correcta fue retirar la declaración muerta, y en los tres retirarla dio 0 diffs, que es la prueba de que estaba muerta.

Un alias puro no se renombra: se borra. Los dieciséis --_mp-* de media-player eran alias de su propia fuente; el veredicto pedía renombrarlos y lo que procedía era eliminar la capa entera.

Lo que la capa posee, el consumidor no lo acuña. listbox se queda en 68 % a propósito: su ritmo de fila es de list-surface. La métrica penaliza hacer lo correcto — registrado en next-features.md §13.

Antes de acuñar un hover-*, mide contra la capa de estado. En listbox el highlighted pinta DOS veces (plano + velo); se dejó sin token para no bendecir un duplicado condenado.

La sonda miente de tres maneras distintas, y todas costaron tiempo aquí: congela transition pero nunca animation; el estado hover se mide después de forzar tallas, así que arrastra la última; y un empate de especificidad resuelto por orden de carga hace que tree-grid dé 0 ó 3 diffs según la recarga. Ante un diff inesperado, lo primero es correr la sonda dos veces sobre el MISMO código. La revisión adversarial encontró y arregló la causa del caso feed (2026-08-21): no era una animación sin localizar sino la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma sema commit-settle anima box-shadow sobre el nodo medido. Sonda y guard esperan ahora a que [data-busy] caiga; con eso, dos corridas del mismo código dan 0 diffs. Y el «falso negativo sin causa» de command.input-border también tenía causa: el centinela ABRÍA haciendo clic en el input, y un clic de ratón sobre un input de texto SÍ casa :focus-visible — la regla de foco pintaba el borde y el token de reposo leía muerto. El instrumento viejo acumulaba 22 falsos negativos de 26; las tres causas están arregladas en theming-sentinel.ts.

LAS INCIDENCIAS NO SE ARREGLAN AQUÍ

Todo lo que este eje destapa y no le toca arreglar está en docs/next-features.md §12 (el contrato de cascada del velo de estado: 131 declaraciones-atajo en 45 componentes, la prop hoverable que no suprime nada, el empate por orden de carga, la banda que mata el hover, el token de tinta que no puede ganar al arquetipo, el thumb de scroll-area sin velo) y §13 (huecos de instrumento y de demo, más los cuatro que salieron de gradient-picker y listbox).

Regla: si una incidencia mueve píxel o toca morfo, se MIDE, se anota ahí y se sigue. No se arregla dentro del commit del componente.

PENDIENTE DE FIRMA (nada bloquea la cola)

  • Diseño de calendar-surface — la firma 1 aprobó el PATRÓN, no un diseño concreto; ese se presenta.
  • scroll-area.thumb-bg-hover — único knob de la firma 3 sin resolver: su thumb lleva archetype: 'thumb', que no recibe velo. Cambiarlo es morfo.
  • Extender la clase system del censo a las capas compartidas, como se hizo con --style-* para los primitivos tipográficos (§13 del registro).
  • El striped de table inerte con RowDetail — FIRMADO Y EJECUTADO 2026-08-21: :nth-child(even of [data-table-row]) con :where(:not([data-selected])), mismo (0,6,0) para no matar el hover. 12 diffs exactos (2 filas × 6 estados); selected y hover verificados.
  • El arquetipo item de table vive en la fila Y en la celda — el velo cae en la celda bajo el puntero (elementsFromPoint, 2026-08-21). La decisión de morfo del hoverable (§12.2) debe contemplar los dos portadores.
  • Que soma lea de un token el gap entre diapositivas de carousel — hoy es canal de valor inline y el trío item-gap* se retiró muerto; darle superficie de tema toca soma (§13).

REGISTRO DE LA SESIÓN DEL 2026-08-20/21 (histórico — no es la agenda)

EJECUTADO 2026-08-21 — la capa calendar-surface (firmada y medida)

La medición cambió el encargo. El handoff la vendía como «una capa nueva, ~220 knobs, 43 % → ~48 %». No lo era: la capa ya existía de hecho, sin nombre ni casa. --calendar-* se emite en :root (76 claves) y sus consumidores no acuñan NADA — range-calendar 114 referencias prestadas y 0 propias, month-grid 77/0, year-grid 77/0, y lo único ajeno que leen es sistema puro (--focus-ring-*, --state-hover). Su 0 % era el artefacto de listbox (§13) pero TOTAL.

Lo que se escribió: lib/calendar-surface.css con el hook data-calendar-surface, cuatro coordenadas por talla (padding, control-size, day-size, font-size, xs..lg — la familia no tiene xl, y la celda va DOS pasos por debajo del bundle de control, deviación que ahora se lee en un sitio en vez de esconderse en cuatro bloques [data-size] idénticos) y la FORMA del anillo de evento. El vocabulario se queda en la entrada calendar de la receta, que pasa a ser la de la FAMILIA: ésta es una capa HÍBRIDA y es lo que la distingue de list-surface / viewport-placement, que componen primitivos y no tienen entrada de receta (76 claves semánticas tienen que seguir siendo alcanzables una a una por config).

El defecto que la justificó, medido: --calendar-event-shadow y --calendar-day-holiday-shadow se emitían con ámbito [data-calendar] y los otros tres los leían desde hosts que nunca llevan ese atributo → variable VACÍA, box-shadow inválido en computed, el anillo sema no pintaba jamás en tres componentes. Ahora la forma vive en la capa y el acento entra por --_calendar-surface-accent, que cada superficie alimenta con su forward de paleta THM-2.

⚠️ Lo que casi se me cuela, y la lección: estampar el hook sólo en los cuatro wrappers standalone dejaba a DatePicker y DateRangePicker con --calendar-padding VACÍA y el panel a padding 0 — renderizan la superficie soma por sus PROPIOS wrappers (date-picker-calendar, -month-view, -year-view, date-range-picker-calendar) y un panel portalado no hereda nada. Son SIETE wrappers, no cuatro. La tentación era enganchar la capa a las cuatro identidades de componente; eso viola la regla 1 de capas compartidas, y la respuesta correcta es un sello por wrapper.

El censo aprendió a no penalizar: LAYER_VOCABULARY en theming-census.ts (mismo precedente que D-TH.2-b con --style-*). Global 43 % → 45 %, calendar 75 % → 84 %. Los tres consumidores siguen leyendo 0 %, y es honesto: con el vocabulario de la capa fuera del denominador sólo les quedan los forwards de paleta THM-2 (que el censo cuenta como private en TODO el catálogo) y literales de layout. Reclasificar el forward de paleta es la misma pregunta un piso más abajo — anotada en §13. list-surface NO se registró: sus consumidores puentean por privados, otra forma, y mueve diez componentes de golpe.

EJECUTADO 2026-08-21 — revisión adversarial de F2-A (§7.7) + guard R-5.4

Molde Sidebar, medición propia contra worktree con solo el CSS revertido a la base del bloque. 34 hipótesis · 26 refutadas · 8 reales. Refutado: el «default idéntico» aguanta en los ocho (0 diffs; los 3 de tree-grid y los 5 de feed son del instrumento, idénticos corriendo dos veces el mismo código), 0 tokens huérfanos de 336, el bundle ES 1:1 con los primitivos, el «verificado a mano» de gradient-picker era verdad. Los 8 reales: (1) el re-point de --command-radius en el Dialog era declaración muerta con comentario falso — retirado, 0 diffs; (2) el 2.25 vertical del carrusel iba a pelo y active-indicator-scale solo alcanzaba la horizontal — tokenizado, 0 diffs; (3) el trío item-gap* de carousel no podía ganar al gap INLINE de soma — retirado, 0 diffs (canal de valor, como --gp-current-gradient); (4) el striped de table es inerte con RowDetail (parity de :nth-of-type) — PENDIENTE DE FIRMA; (5-7) el instrumento tenía 22 falsos negativos de 26 (clic-foco en el input · override solo en [data-{c}] · pseudos invisibles) y la no-determinación de feed era la demo cargando (commit-settle sobre [data-busy]) — las cuatro causas arregladas en theming-sentinel.ts y la sonda; (8) tres cifras de especificidad del registro §12 estaban desviadas en uno y el arquetipo de la fila de table vive TAMBIÉN en la celda — corregidas. R-5.4 nace: npm run theming:sentinel -- <c> <url>, ledger theming-sentinel-exceptions.ts con una razón medida por token (46 excepciones en los ocho, cinco entradas mías salieron STALE y las borró el propio guard), doctrina en recipe-contract §4. Dos hallazgos los produjeron los propios arreglos: el blur apaga focus-input-border (adjudicado) y la espera de asentado desmonta el spinner de feed (adjudicado). Censo tras la retirada: carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto.

Lo que sigue es el detalle de cómo se llegó aquí: las firmas, el codemod, el guard, y el bloque componente a componente. Se conserva porque cada decisión lleva su porqué medido, pero la agenda es lo de arriba.

EJECUTADO 2026-08-20 — vocabulario de tokens: D-TH.6 (codemod) + R-5.3 (guard)

Por qué antes que nada (conversación 2026-08-20): el canon de nombres existe (theming §6.7 slots + recipe-contract §1 dimensionales) pero no tiene guard, y la medición del 2026-07-01 ya demostró que canon sin guard deriva 30-85 %. Está derivando: el backfill de ayer escribió la forma DOCUMENTADA (modificador delante, 36 claves) mientras el catálogo habla al revés — cada componente que se corrija sin firmar esto ensancha la brecha. El guard de eventos (eidos-event-vocabulary.ts) es el precedente: nació de un commit-resize muerto tres meses en una receta; R-4.4 es el otro (una regla de nombres ya guardada, deuda muerta «en el mismo pass, sin allowlist»).

FIRMADO 2026-08-20 — el slot de tinta es fg; color como slot MUERE. La sesión del 20 leyó lo que la del 19 no había leído (arts/color, architecture/eidos.md entero, motion §17, §25/§38) y encontró que la pregunta central no era «normalizar hacia lo documentado» sino una CONTRADICCIÓN entre dos doctrinas: theming §6.7 r7 (fg) contra el principio de plataforma del px/py («el token se llama como la propiedad»: color). El autor adjudicó fg: el principio de plataforma gobierna los ejes DIMENSIONALES, no la pareja bg/fg (si la gobernara, bg sería background-color); color es la palabra más sobrecargada del sistema (--color-*, data-color, 9 roles, arts/color); on-fg compone; Chakra v3/Panda hablan la pareja.

Inventario CORREGIDO 2026-08-20 (instrumental: los scripts/__names-*.ts de esta sesión — census, slots, owners, audit7, audit7b; el exacto y regenerable lo produce --names en el paso 1). Sobre 3.333 claves públicas, 301 desviadas en 66 componentes — 193 con color como slot, 78 con el modificador DETRÁS a nivel de receta, 30 con ambas. La semilla __names-inventory.ts decía 370 porque contaba como desviadas 47 claves {rol}-{slot-de-rol} que son CANÓNICAS (button.primary-solid-hover, toggle.palette-hover… — COLOR_ROLE_SLOTS pone el modificador detrás POR CONSTRUCCIÓN) — un codemod sobre las 370 las habría roto. Adopción viva: 196 -color contra 55 -fg (las fg son mayormente las del backfill de este eje). Peores: tag-group 22 · toast 15 · field 13 · calendar 13 · tabs 12. Tres familias, tres gramáticas (el guard codifica las tres): rol/paleta = modificador DETRÁS (canon) · sistema (--state-hover, --opacity-hover) = detrás, fuera del alcance del guard · recipe-level = §6.7 r7 (delante: hover-bg, 13 claves conformes contra 22 bg-hover).

Falsos amigos (22) — FIRMADOS en el acta: *-focus-ring-color (3, espeja la familia del SISTEMA) · aura.orb-color-* (6, color como sustantivo del orbe) · gradient-builder.stop-color-* (4, stop-color ES la parte — verificado: gradient-builder-stop-color.svelte + parts DOM) → se quedan · los 7 con modificador «fuera de vocabulario» resultaron ser DOS clases que el morfo ya resuelve (valores de data-state/ejes declarados: active, partial, read, failed; y current = aria, que entra al vocabulario universal) más una de pseudo-partes (played/buffered) → se renombran (destinos en el acta) · background.scrim-color-on-{dark,light} (2, chocaban con el prefijo on-) → scrim-fg-over-{dark,light}.

Hovers neutros — firmado: NO se renombran, MIGRAN: §38 + R-4.3 deprecan el hover neutro por componente (es la capa --state-*). dropdown-menu.item-bg-hover, context-menu.item-bg-hover, scroll-area.track/thumb-bg-hover, splitter.handle-bg-hover… la firma 3 del acta ordena su migración a la capa de estado (captura + diff explicado por componente). El codemod los EXCLUYE y el --names los lista aparte como cola de esa migración.

LOS CUATRO PASOS ESTÁN HECHOS (f09e04fab acta · bd916ef3f codemod · 7781d6e4c guard + doctrina, sin pushear). Lo que queda del bloque, y sólo esto:

  • El tercer muro espera a la firma 3. El tipo en defineRecipes (molde PhysicalAxisKey) está escrito y probado: dispara sobre 17 claves, que son los hovers neutros con color en cola de migración. Meterlo hoy rompe npm run check a todo el mundo. Entra cuando la migración las vacíe — dos líneas: una rama ColorSlotKey y las tres excepciones firmadas (*focus-ring-color, orb-color-*, stop-color-*) deletreadas.
  • __names-inventory.ts quedó superado por theming-census --names (contaba 370 porque metía las 47 role-slot). No se borra sin que lo digas.

El registro de los pasos, para quien audite después:

Los pasos, en orden y con su gate (el paso 0 murió el 2026-08-20: D-TH.6 está firmada COMPLETA — ver §«Firmas del 2026-08-20»; la gramática ejecutable es: slot fg · delante lo interactivo, detrás lo dimensional/contextual · modificador ∈ universal (+current) ∪ data-state/ejes del morfo · rol/paleta detrás · hovers neutros excluidos · renombres nominales de los ex-falsos amigos en el acta):

  1. --names en el censo: el inventario exacto por clase de desviación, regenerable, con las 47 role-slot reconocidas como canónicas, los falsos amigos resueltos por el acta (los 3 grupos que se quedan excluidos; los 4 renombrados contados como desviación con su destino) y los hovers neutros listados APARTE (van a la firma 3, no al codemod). Muta-prueba del instrumento: 3 desviadas conocidas salen, 3 role-slot NO salen.
  2. Codemod value-preserving (molde: px/py del 2026-07-06 y su triple muro): -color → -fg (196 + 49 mediales, menos los 3 grupos que el acta deja quedarse y los hovers neutros que migran aparte), los renombres nominales del acta (rating-group · chat-message · breadcrumb · waveform · scrim-fg-over-*), y modificador detrás → delante a nivel receta (bg-hover → hover-bg, 22 claves); renombra la clave en recipes/base.ts Y todos los consumidores (var(--{c}-{clave}) en css/svelte/ts/md — con git ls-files, no de memoria; los .md de HISTORIA — changelog, errores-toxico, PLAN-affix/background — NO se reescriben), regenera, y verifica: diff de generated/ = renombres 1:1, censo global IDÉNTICO (un rename no cambia alcance), sonda de computed en 3 componentes renombrados = 0 diffs. Un commit, script commiteado como registro.
  3. Guard R-5.3 en component-audit — de GRAMÁTICA, no de lista (a diferencia del de eventos, que valida pertenencia a un vocabulario cerrado), y POR FAMILIA: {rol|palette}-{slot-de-rol} con modificador detrás (COLOR_ROLE_SLOTS) ∪ recipe-level [variante-]?[modificador-]?[parte-]?[slot][-talla]? donde talla ∈ las 7 canónicas, modificador ∈ vocabulario cerrado y DELANTE, slot ∈ (§6.7 recipe-level ∪ ejes dimensionales de recipe-contract §1), y la PARTE contra compileMorfo(morfo).parts + la allowlist eidos-only (el mismo criterio que eidos-lint). Severidad error directo: tras el codemod no hay deuda que tolerar, y la rampa warn es como estos guards mueren. Muta-prueba obligatoria de tres caras antes de darlo por bueno: -bg-hover rojo · trigger-color rojo · primary-solid-hover VERDE (memoria a-guard-that-inspects-nothing-passes).
  4. Docs en el mismo pass: recipe-contract §1 gana la fila del modificador (hoy sólo la insinúa §6.7) y §4 la fila R-5.3; theming §6.7 nota fechada con la firma de fg y el acotamiento del principio de plataforma a los ejes dimensionales; T-TH.2 del plan se cierra.

Gates del bloque entero: censo global idéntico antes/después del codemod · suite eidos sin rojos nuevos · component:audit sin fallos nuevos fuera de R-5.3 · docs:check 0 · rtl:check 0.

Después — por dónde seguir

  1. node --import tsx/esm scripts/theming-census.ts — debe dar 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56 sin contrato (antes del codemod; tras él, el MISMO alcance con nombres nuevos). Si no cuadra, alguien tocó recetas: regenerar el informe (--report) antes de nada.
  2. Abrir la ficha del componente que toque y leer su §5 Veredicto: dice qué de la propuesta es correcto, qué está mal y qué espera firma. Está verificado contra el CSS; la §4 generada NO se sigue a ciegas. Ojo: los veredictos se escribieron ANTES del acta del 2026-08-20 — sus «espera firma» se cruzan con §«Firmas del 2026-08-20» (la mayoría ya están resueltos: calendar, Field, hover→estado, D-TH.2) y sus nombres propuestos se pasan por la gramática firmada (p. ej. el guide-color de la fila tree-grid de la cola es guide-fg).
  3. Ejecutar con el protocolo §7 entero. Un componente = un commit.

PRIMERA POSICIÓN — repaso de los 7 ya corregidos contra la gramática nueva

Encargo del autor (2026-08-20): revisar lo ya ejecutado por si incumple las reglas nuevas. Auditados los 7 con scripts/__names-audit7.ts y __names-audit7b.ts (claves públicas + privados + consumo en css/README). Resultado: 6 de 7 limpios, 1 hallazgo real.

componente claves veredicto
combobox 106 ✅ limpio — ya habla fg, sin modificador detrás, privados sanos
natural-time-picker 62 ✅ limpio
date-range-picker 24 ✅ limpio
time-range-picker 22 ✅ limpio
proof-of-human 20 ✅ limpio
time-picker 16 ✅ limpio
gradient-builder 72 ⚠ 1 corrección (abajo)

Que los 6 salgan limpios NO es casualidad ni suerte: el backfill de este eje venía escribiendo fg (55 de las ~55 claves -fg del catálogo son suyas), que es exactamente lo que la firma ratificó. La firma valida el trabajo hecho, no lo invalida.

La corrección — gradient-builder.checker-color → checker-fg (1 clave, consumida 4 veces en gradient-builder.css:29-32). Es el color de los cuadros del damero de transparencia: color ahí es slot, no sustantivo — cae de lleno en la regla firmada. Las otras 4 claves que el audit marca (stop-color-title-font-size, -font-weight, -fg, stop-color-sliders-gap) son falso amigo verificado: stop-color es la PARTE (gradient-builder-stop-color.svelte, [data-gradient-builder-stop-color-*]), y una de ellas ya termina en -fg correctamente. No se tocan.

Ejecución: va DENTRO del codemod del paso 2 (misma mecánica, mismos gates), no como commit aparte — es una clave, y sacarla del codemod duplicaría la verificación. Si el codemod se retrasa por las firmas pendientes, se ejecuta sola con el protocolo §7.

BLOQUE F2-A — CERRADO 2026-08-21 (preparado el 20, ejecutado el 21)

Los ocho, con su commit y su alcance final:

componente antes → después commit
pieza 0 (censo) 37 % → 38 % bd032534b
command 0 % → 98 % 4ddea7fe2
table 0 % → 86 % 564133b48
media-player 0 % → 88 % b7a4cef08
tree-grid 0 % → 95 % 9748733ad
gradient-picker 0 % → 88 % d177ee331
listbox 0 % → 68 % 3b325c439
carousel 0 % → 77 % 4cab77667
feed 0 % → 94 % 34a95a550

Global 37 % → 43 %; sin contrato 56 → 49; al 100 % 6 → 7. Más 90aa6dc5f (el TokensPanel que las demos enseñan) y 8324e2ced (el registro de incidencias).

El plan del bloque, tal como se preparó:

El primer bloque del backfill bajo TODAS las firmas. Ocho componentes con veredicto §5 verificado + una pieza de instrumento que va primero. Suma 330 knobs hoy a 0 %; al cerrar, el global debe rondar 37 % → ~42 %. Cada componente: protocolo §7 entero, un componente = un commit, con la gramática firmada (¡el guard R-5.3 y el muro de tipos ya vigilan!).

Pieza 0 — el instrumento ANTES del bloque (D-TH.2-b, firmada). La familia tipográfica B5 mide su superficie por --style-* como sistema transversal: el censo añade --style-* a la clase system PARA los primitivos tipográficos (heading, text, display, code, s-text…), con gate: las cifras de TODOS los demás componentes quedan idénticas, y heading pasa de «36 privados» a su verdad (consume la capa semántica). Va primero porque cambia el suelo del censo — hacerla a mitad de bloque falsearía los antes/después.

# componente knobs defectos medidos (censo + ficha) corrección firmada por el veredicto
1 ✅ command 52 SIN bloque en base.ts; 35 globales · 9 privados sin derivar · 6 literales contrato entero con talla resuelta (patrón Sidebar); radius con la adaptación al Dialog como CONTEXTO (lee un público de dialog, legítimo — no partir en dos); list-padding-inline + scrollbar-inset (patrón combobox); sonda con el palette ABIERTO
2 table 46 23 privados · 14 globales · 5 literales root-height-{k} es la fila → row-height-{k} al bundle; el prefijo root- sobra en el resto; talla
3 media-player 45 37 globales · 8 literales; privados --_mp-* ABREVIADOS (§6 r5 los prohíbe) renombrar --_mp-* al nombre completo (⚠ prettier: la anotación /* literal: */ en la MISMA línea — trampa de natural-time-picker) + tokens; su acento theme-stable está FIRMADO en base.ts — no tocarlo; el ⚠ de bg son DOS knobs (superficie y velo)
4 tree-grid 42 29 privados · 8 globales root-bg-image son las guías de indentación: el knob es la tinta (guide-fg — era guide-color pre-acta), los 5 gradientes se quedan en la receta; talla
5 gradient-picker 38 privados --gp-* ABREVIADOS · 23 globales renombrar --gp-* + tokens; content-width es por talla (20/18/22rem), no un solo knob
6 listbox 38 23 globales · 9 privados; una fila FALSA en la propuesta tokens; retirar la fila font-size = var(--list-font-size): es público de la capa list-surface — un consumidor no acuña lo que la capa posee
7 carousel 36 12 globales · 14 privados · 5 literales tokens + talla; los indicator-width-{k} son geometría propia (rem), ni bundle ni desviación
8 feed 33 16 globales · 13 privados tokens + talla; el título es por talla y el feed tipa por debajo del 1:1 (md → sm), verbatim — no «corregirlo» al 1:1

Cada componente engancha su tab Tokens en el MISMO commit (firmado 2026-08-20, a raíz de que el autor viera que la demo de command no enseñaba su superficie de tema — y resultó que ninguna lo hacía). Es una línea: <TokensPanel component="x" stage={stageRef} /> más el botón del tab; el panel (web/routes/uix/lib/TokensPanel.svelte) lee el contrato vivo con eidos.getRecipeTokens(), así que no hay tabla que mantener. Un componente temable cuya demo esconde sus tokens está a medio entregar.

Gates de cada componente (§7.4/7.5, sin excepciones): censo --only {c} al 100 % o excepción escrita · sonda antes/después = 0 diffs · centinela por token nuevo · component:audit PASS (R-5.3 incluida) · eidos-lint 0 invalid · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero · README del componente con la tabla «Talla y tema» (molde navigation-menu). Al cerrar el bloque: revisión adversarial §7.7 (molde Sidebar: refutar «idéntico», «alcanza», «píxel arriba» con medición propia).

Al cerrar F2-A, lo desbloqueado por las firmas espera en este orden:

  1. Diseño de la capa calendar-surface (firma 1) — se PRESENTA antes de escribirla (nombre, ejes, qué posee; molde list-surface y sus 4 reglas). Desbloquea los tres gordos del catálogo: range-calendar (98) + month-grid (61) + year-grid (61) y media date-range-picker — 220 knobs, el salto de ~42 % a ~47 %.
  2. Mandato Field ejecutable (firma 2) — field-langs (41) + las mitades de time-picker / time-range-picker / date-range-picker.
  3. El resto del orden B1-B8 de D-TH.4 (grid-list 32 · textarea 28 · …).

AL TERMINAR EL EJE ENTERO — partir recipes/base.ts por componente

Firmado 2026-08-20, a raíz de la pregunta del autor «¿por qué todos los tokens viven en un fichero?». Va al final de TODO el themeable, no al cerrar un bloque: mientras quede un componente por tokenizar, ese fichero se toca en cada commit.

recipes/base.ts son 6.223 líneas y 128 componentes, y NO es un fichero de tokens: es el valor del campo recipes de EidosConfig, serializable, que un tema hidrata. Por eso los valores no pueden mudarse a components/{x}/ — se intentó y se retiró (architecture/eidos.md: «The old tokens/components/* were retired»). Lo que sí puede repartirse es la AUTORÍA:

// lib/recipes/table.ts
export const tableRecipe = { 'row-height-md': '…', … };
// lib/recipes/base.ts  — el valor sigue siendo UNO
export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ table: tableRecipe, … });

Verificado antes de proponerlo, no supuesto: el muro de tipos de defineRecipes SIGUE disparando cuando las claves llegan por spread desde un fichero suelto — probado con trigger-color (slot color) y padding-x (eje físico), ambas dan error de compilación igual que inline. El único consumidor sensible a la forma es eidos-purge.ts, que hace Object.keys() y es indiferente. recipe-css-contract, el censo, R-5.3 y getRecipeTokens() leen el objeto compuesto.

Los tres costes que paga el fichero único, todos medidos en la sesión del 2026-08-20: casi entra un SEGUNDO bloque table que el catálogo habría descartado en silencio (la trampa está anotada abajo); una coma perdida rompió el catálogo entero dos veces; y es el punto de conflicto de todas las sesiones concurrentes. Repartido, un error afecta a un componente.

Por qué al final y no antes: son ~6.000 líneas movidas en el fichero que cada commit del backfill toca. Hacerlo con componentes pendientes garantiza conflicto con toda sesión que esté tokenizando; con el eje cerrado es un commit mecánico y aislado.

Gate: THEME_BASE_RECIPE_TOKENS idéntico antes/después (comparación ESTRUCTURAL del objeto, no del fichero) · censo global idéntico · generated/base.css sin un solo cambio · el muro de tipos con su muta-prueba (las dos claves malas siguen sin compilar).

FIRMA 3 en ejecución — hover → capa de estado (act. 2026-08-20)

El diagnóstico cambió al empezar. La firma decía «migrar los hovers neutros a la capa de estado». Al hacerlo salió que el velo del sistema no llegaba a ninguno, y no por el hover: por la regla BASE. archetypes.css pinta el velo con :where(...), peso (0,0,0); cualquier receta que declare el ATAJO background: sobre ese mismo nodo fija background-image: none con (0,1,0) y lo cancela SIEMPRE — ni en hover ni nunca. El hover propio no era un capricho: era la cicatriz de un velo que no llegaba.

Así que cada migración son DOS cambios: background: → background-color: en la base, y fuera el hover propio.

componente commit medido
checkbox b35855c20 8 diffs, todos en hover: velo + fondo base (2.436 valores)
splitter c66b73810 2 diffs en el asa (812 valores)
switch 3219a19c3 2 diffs en el track off (377); el swap del checked, verificado
date-range-picker 0c80a8efb sin medir el pintado — la demo no monta kind month/año

Los 6 knobs de la cola eran 47 hasta que se midió la PROPIEDAD: el velo es background-image, así que un hover de borde (22) o de tinta (19) no es la capa de estado, sea cual sea su valor. Esos 41 eran deuda de nombre y se renombraron (93017975c).

Las incidencias van al registro, no al eje

Todo lo que este eje destapa y NO le toca arreglar está registrado en docs/next-features.md §12 y §13 — con su medición, no como impresión:

  • §12 · El contrato de cascada del velo de estado. Seis incidencias que son la misma: nadie fijó cómo compone el velo de archetypes.css con las reglas de una receta. Incluye las 131 declaraciones-atajo en 45 componentes, la prop hoverable que no suprime nada, el empate (0,5,0) que resuelve el orden de carga, la banda de tree-grid que mata el hover, el token de tinta que no puede ganar al arquetipo, y el thumb de scroll-area sin velo al que migrar.
  • §13 · Huecos de instrumento y de demo. Lo que hizo que una medición mintiera o no existiera: falsos negativos del centinela, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un <tr>, y la lección general — una sonda sobre una parte que la demo no monta compara CERO valores y pasa.

Regla para las sesiones de este eje: si una incidencia mueve píxel o toca morfo, se MIDE, se anota ahí y se sigue; no se arregla dentro del commit del componente.

Lo que queda de la firma 3, y por qué no lo he hecho

  1. scroll-area.thumb-bg-hover — pide DECISIÓN de morfo. El thumb lleva archetype: 'thumb', que NO recibe velo. Migrarlo exige cambiar su arquetipo, y un arquetipo es morfo: se presenta, no se toca de oficio (§7.3-9). La alternativa es dejar su hover como excepción anotada.
  2. El hallazgo sistémico: 141 declaraciones en 45 componentes cancelan el velo del mismo modo, medido con scripts/__statelayer-shorthand.ts (que comprueba contra el morfo que el atajo cae sobre el nodo VELADO, no sobre cualquiera). Peores: radio-group 12 · tag-group 8 · checkbox 7 · table 7 · combobox 6 · tabs 6. Esto explica la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. Es un barrido de catálogo que mueve píxel en 45 componentes: no entra en la firma 3, que se firmó sobre unos knobs concretos.
  3. Hueco de demo: date-range-picker no expone kind='month'/'year' (norma N-6), así que la mitad de su API no se ve ni se mide.

Firmas del 2026-08-20 — el acta (las 14, «FIRMO TODAS»)

Presentadas con recomendación fundada y firmadas en bloque por el autor. El detalle razonado de cada una vive en la conversación del 2026-08-20 y en la fila correspondiente del plan §4; aquí el QUÉ ejecutable:

  1. Familia calendar = capa compartida (molde lib/list-surface.css: attr de capa, un eje = token público + ranura privada var(--_x, var(--x)), guardada por shared-layer-contract.test.ts + layer:check). NUNCA ~150 alias --calendar-*. Desbloquea range-calendar (98), month-grid (61), year-grid (61) y media date-range-picker. El diseño de la capa (nombre, ejes, qué posee) se presenta antes de escribirla.
  2. Mandato Field: ejecutable. Los x-field/pickers componen el wrapper Field; las alturas de segmento salen del eje size a nivel familia (--field-control-height-{k}); muere la re-duplicación height-{xs..xl} por componente (theming-audit §2-B: computaban auto desde su nacimiento).
  3. hover → capa de estado, donde reaparezca (empezando por los 3 knobs de combobox). Mueve píxel hacia el canon §38/R-4.3 ⇒ cada migración lleva captura antes/después y su diff explicado en el commit. Desbloquea la poda D-2 (changelog §38 ◇: los --{x}-bg-hover huérfanos, incluido pagination.selected-bg-hover = capa sobre acento).
  4. D-TH.6 completa — slot fg + modificador DELANTE con la regla: delante lo interactivo, detrás lo dimensional y contextual; modificador válido ∈ vocabulario universal (+current) ∪ valores data-state/ejes del morfo del componente; waveform.played/buffered = pseudo-partes; scrim-color-on-* → scrim-fg-over-{dark,light}; hovers neutros FUERA del codemod (migran por la firma 3). Renombres nominales de los ex-falsos amigos: item-fg/active-item-fg/partial-item-fg (rating-group), read-status-fg/failed-status-fg (chat-message), current-link-fg (breadcrumb), played-fg/buffered-fg (waveform). Se quedan: *-focus-ring-color (sistema), aura.orb-color-* (sustantivo), gradient-builder.stop-color-* (parte).
  5. D-TH.1 R-5 dura · D-TH.3 tracks WIP en censo, error sólo fuera de carril · D-TH.5 el default no cambia (única excepción: la firma 3, con captura).
  6. D-TH.2 con la opción (b) de heading: la familia tipográfica B5 mide por --style-* como sistema transversal — no se acuñan --{c}-* tipográficos. La lista KNOB_PROPS es el perímetro; cambios = firma.
  7. D-TH.4 con enmienda: la cola revisada de este CONTINUE prioriza sobre el orden de familias B1-B8.
  8. D-TH.7 con enmienda: R-5.3 a error directo tras el codemod; R-5.1/5.2 esperan F3.
  9. D-TH.8: panel de tokens en /temas, en F4.

Lo ejecutado (censo antes → después)

Bloque del vocabulario, 2026-08-20 (tres commits, sin pushear):

commit qué
f09e04fab el acta de las 14 firmas + la revisión de los 7 ya corregidos
bd916ef3f codemod: 269 claves · 793 referencias · 101 ficheros · alcance IDÉNTICO · 0 diffs de computed
7781d6e4c R-5.3 en error (muta-prueba de 3 caras) + recipe-contract §1/§4 + theming §6.7 + checklist

Lo que el bloque enseñó (más caro de lo que parece):

  • El plan del codemod se escribe ANTES de tocar nada. Releer el clasificador después da CERO desviaciones —ya están renombradas— y el verificador del diff pasa sobre el vacío. Costó dos vueltas.
  • Un valor puede cambiar de texto sin cambiar de significado: 22 privados reapuntan a nombres nuevos. El gate normaliza por el plan antes de comparar, o los cuenta como cambio de valor y da rojo en falso.
  • No pasar prettier sobre lo que el codemod tocó. El árbol tiene CRLF (autocrlf=true) y 88 de 101 ficheros ya salían «sucios» ANTES; un --write en masa reformateó 300 ficheros de deriva ajena. Se midió lo que importaba —¿alarga el renombrado alguna línea?— y la respuesta fue no en todos, así que prettier no hacía falta.
  • Clasificar un hover por el NOMBRE es clasificarlo mal: dropdown-menu.item-bg-hover lee var(--color-primary-element) — es el palette swap con valencia de recipe-contract §2, no el hover bespoke que §38 deprecó. Lo decide el VALOR. Yo lo había listado mal en este mismo handoff.
componente alcance commit
gradient-builder (piloto) 0 % → 80 % 3cdb5b5f0
combobox 0 % → 76 % cef2d32f1
natural-time-picker 0 % → 61 % e774ca7e8
date-range-picker 0 % → 42 % 07778b597
time-range-picker 0 % → 19 % 68eb8af26
time-picker 0 % → 18 % e4fcdd62e
proof-of-human 0 % → 14 % ae91573c3

Global del catálogo: 33 % → 37 % (1.621 → 1.841 knobs públicos). Informe + instrumento: 2591ec2f0; revisión de las fichas 11-20: 3bfe0d413.

El instrumental (vive en scripts/, prefijo __ = temporal del eje)

node scripts/__theming-probe.ts <componente> <salida.json>   # sonda antes/después
node scripts/__theming-diff.ts <antes.json> <después.json>   # el gate: diff VACÍO
npm run theming:sentinel -- <componente> <url>               # guard R-5.4: ¿alcanza cada token? (ya no es `__`)
node scripts/__shot.ts <componente> <salida.png>             # captura 2× del stage

Se ejecutan con node, no con tsx (tsx inyecta helpers que rompen page.evaluate) y desde la raíz del repo, contra el dev server en localhost:5173. Los scripts/__dbg*.ts y __sentinel-targeted*.ts son de un solo uso (verificaciones dirigidas de ayer); no se commitean.

Lo que el instrumento aprendió a fuerza de mentir

  • La sonda congela transition, NUNCA animation: una propiedad transicionada devuelve su valor INICIAL justo tras escribir el token (un token vivo parecía muerto), pero congelar animation impide que Presence monte el panel — y entonces mides un popup que no existe.
  • Un token RESUELTO no se mueve desde :root: se declara en [data-{c}] o en su parte, así que el tema mueve la coordenada. El centinela escribe en :root y en el host, y aun así los resueltos salen «muertos» por diseño.
  • Un panel portalado no ve el ámbito del componente: sus tokens se emiten en :root (o con parts), y un override scoped no lo alcanza.
  • La demo puede tapar el componente: proof-of-human declara su propio min-block-size y fondo sobre el stage — ahí el centinela no sirve, y se dice.
  • Un valor centinela igual al real lee como «no efecto» (9999px contra un --radius-full que ya era 9999px).
  • Los tokens que SON la transición no se pueden medir congelando (2026-08-23): transition-duration / transition-ease salen muertos SIEMPRE, en cualquier componente, porque el congelado es el arreglo que hace medible todo lo demás. Se miden en una pasada aparte, sin congelar (color-picker: 0.12s → 11.5s).
  • Ni !important en línea mueve una propiedad EN TRANSICIÓN si lees en el acto (2026-08-23): la lectura devuelve el valor viejo y parece un token imposible de mover. Pasó con el fondo del cuentagotas: tres diagnósticos falsos hasta congelar.
  • La sonda es CIEGA a un puente de talla por contexto (2026-08-25, form): fuerza la talla ESCRIBIENDO data-size sobre cada nodo casado que ya lo lleva (__theming-probe.ts:279-281) — y una parte que COMPONE el Button lleva data-{c}-… y data-size. Se lo pisa DESPUÉS del render, así que mide el mismo atributo forzado antes y después: el diff sale 0 por construcción, no por ausencia de efecto. Medido: 0 diffs sobre 832 valores mientras el botón pasaba de 36 px fijos a 26/30/36/44/52. Para esta clase de firma el instrumento tiene que mover el chip REAL de la demo (el prop), nunca el atributo. La sonda estándar sigue valiendo como gate de «no se movió nada COLATERAL» — que no es poco, pero no es la prueba.
  • --font-size-lg / -xl son clamp() FLUIDOS (18→20 y 24→28): una medida de letra en esas dos tallas no es comparable entre informes si el viewport no viaja con ella. Medido: a 900 px dan 19,05 / 26,10 y desde ~1200 px saturan en 20 / 28. Costó una desviación aparente de +2 px contra la cifra de un expediente que no declaraba su ancho. Declara el viewport en toda medida de fuente lg / xl.

Trampas que costaron un commit cada una (no repetirlas)

  • Clave duplicada en recipes/base.ts: añadir un bloque que ya existía lo descarta en silencio (gana la segunda). Guard antes de generar — y que cuente cuántos bloques ve: la versión con \t que vivió aquí hasta el 2026-08-21 no casaba NINGUNO (grep -E lee \t como una t literal) y salía «vacío = OK» sin haber mirado nada — el antipatrón del guard que inspecciona el vacío, en el propio handoff.

    node -e "const t=require('fs').readFileSync('src/uix/eidos/lib/recipes/base.ts','utf8').replace(/\r\n/g,'\n');const k=[...t.matchAll(/^\t'?([a-z0-9-]+)'?: \{/gm)].map(m=>m[1]);const d=k.filter((x,i)=>k.indexOf(x)!==i);console.log('bloques',k.length,'| duplicados',d.length?d:'ninguno')"
    

    Debe imprimir los ~130 bloques y duplicados ninguno. Si dice 0 bloques el guard está roto, no es que el fichero esté limpio.

  • Dejar vivos los bloques [data-…][data-size='…'] del CSS al emitir la cascada por el TSC: pisan los tokens nuevos con los valores viejos, y el diff de computed da 0 porque la ruta vieja sigue mandando. Lo cazó el centinela.

  • Correr component:audit DESPUÉS de prettier, no antes: al renombrar un privado a su nombre completo, prettier partió tres declaraciones en varias líneas y separó la anotación /* literal: … */ de su valor — R-2.1 la exige en la MISMA línea, así que el guard pasó a ver colores crudos donde antes veía excepciones anotadas. Se arregla con /* prettier-ignore */ sobre la declaración: NO acortando el comentario ni tocando el valor. Regresión real en natural-time-picker, cazada un día tarde por correr el audit antes del formateo.

  • Backticks en node -e "…" desde bash: se ejecutan y vacían el texto. Para editar markdown con código, usar el editor, no el shell.

  • prettier --write sobre un README no protege de que un Edit se coma un encabezado: comprobar grep -c "^## Gaps" después de insertar secciones.

  • Lo de ayer sigue vigente: medir el nodo que el usuario señala (píxel arriba), el hover es del sistema, el default no cambia, rama compartida sin stash ni --amend, check por fichero.

Fuentes

  • Plan y decisiones: PLAN-theming.md · informe: docs/audit/theming/README.md.
  • Auditoría del SISTEMA (fase 1, cerrada): theming-audit.md — §B familia calendar, §5.3-3 mandato de Field.
  • Doctrina: docs/theming/reference.md (§5 talla, §6 nombres, §38 capa de estado) · docs/canon/recipe-contract.md · docs/canon/tsc.md · docs/architecture/eidos.md (vertebración tipográfica, columna unused) · src/uix/eidos/components/README.md (capas compartidas).
  • Precedente de la forma por talla: PLAN-sidebar.md §3 + F3 y el bloque sidebar de recipes/base.ts.

Powered by TurnKey Linux.