docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/ * *
* theming - census — how much of each recipe ' s APPEARANCE can a theme reach
* through the component ' s own public tokens ?
*
* node -- import tsx / esm scripts / theming - census . ts # summary + table
* node -- import tsx / esm scripts / theming - census . ts -- json # machine - readable
* node -- import tsx / esm scripts / theming - census . ts -- only tabs
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* node -- import tsx / esm scripts / theming - census . ts -- report # docs / audit / theming /
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* node -- import tsx / esm scripts / theming - census . ts -- names # naming grammar ( D - TH . 6 )
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* node -- import tsx / esm scripts / theming - census . ts -- residue [ out . tsv ]
* # privates still to adjudicate
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
*
* WHY . The recipe contract already says every recipe declares its knobs in
* ` lib/recipes/base.ts ` ( recipe - contract § 1 , theming § 6 ) , and the R - 4 . x guards
* make sure nothing is a literal . But nothing asserted that a knob is REACHABLE
* by a theme : a recipe that binds every knob to a global primitive
* ( ` var(--radius-md) ` ) or to a private ( ` --_c-radius ` ) passes every guard and
* can only be themed by moving the whole system . ` navigation-menu ` shipped that
* way until 2026 - 08 - 19 ( PLAN - theming . md § 0 ) . This script is the baseline and the
* per - component gate of that axis : coverage must go UP , literals must go DOWN ,
* and the number must be reproducible by anyone — never quoted from memory .
*
* WHAT COUNTS . A declaration is a KNOB when its property is one of
* ` KNOB_PROPS ` ( appearance : fill , ink , border , radius , shadow , spacing , type ,
* size ) . Layout mechanics ( display , position , flex , grid , overflow , … ) are not
* knobs . Each knob is classed by WHERE its value comes from :
* public — ` var(--{c}-…) ` the component ' s own contract token
* private — ` var(--_{c}-…) ` a component - internal name ( a theme cannot
* name it ; fine ONLY if the private derives
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* from a public — the report ' s § 3 per component )
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* system — a transversal system token the recipe contract says a recipe
* CONSUMES rather than owns : state layer , focus ring , depth
* planes , motion , z - bands , opacity , shape , floating gap
* global — any other ` var(--…) ` : a raw primitive ( ` --space-* ` ,
* ` --radius-* ` , ` --color-* ` , ` --font-size-* ` , ` --size-* ` … )
* literal — no ` var( ` at all ( ` 8px ` , ` 1.25 ` , ` #fff ` )
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* exception — a SIGNED deviation , in either of the canon ' s two written
* forms : a literal annotated ` /* literal: <reason> * / `
* ( recipe - contract § 3 ' s valve , the same one ` component-audit `
* honours ) or a residue private annotated ` /* private: <reason> * / `
* ( the same valve one class over , 2026 - 08 - 25 — see
* ` ANNOTATED_PRIVATE ` ) . A signed deviation is not debt .
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* structural — EVERY knob of a component whose 0 % is its NATURE and not its
* debt , because it has no contract to write : the signed list is
* ` STRUCTURAL_COMPONENTS ` below .
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* bridge — ` var(--_{c}-palette-{slot}) ` : the private the THM - 2 palette
* forward WRITES . The recipe reads the mechanism of the
* per - instance palette , not a name of its own .
* channel — ` var(--_{c}-x, fallback) ` where NOBODY declares ` --_{c}-x ` :
* soma or the wrapper writes it per INSTANCE ( a % , a measured
* rect , the size a prop asks for ) . A theme must not reach it .
* Reach = public / ( public + private + global + literal ) . ` system ` ,
* ` exception ` , ` structural ` , ` bridge ` and ` channel ` are reported but excluded
* from the ratio on purpose : the first is themeable at the system level by
* design ( recipe - contract § 2 ) , the second is a deviation the canon already
* accepted in writing ( § 3 ) , the third has nothing a theme could name in the
* first place , and the last two are the two shapes of private the doctrine of
* this axis already adjudicated as NOT debt ( see ` PALETTE_BRIDGE ` below ) .
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
*
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* REPORT . ` --report ` writes the audit under ` docs/audit/theming/ ` : a root
* ` README.md ` with the whole - catalogue view , and one ` {c}.md ` per component
* with its analysis AND a proposed correction ( the token to declare , its
* value - preserving definition , the TSC scope ) . The proposal derives names from
* recipe - contract § 1 + theming § 6.7 — it never invents a name , and it marks
* ` ⚠ decisión ` wherever the doctrine does not decide for us .
*
* LIMITS ( honest ) . A regex over CSS text : a declaration carrying SEVERAL tokens
* is classed by the first class that matches , in order public → private →
* system → global ( so ` outline: var(--focus-ring-width) solid
* var ( -- color - primary - border ) ` reads as ` system ` ); values inside ` @keyframes `
* count like any other ; another component ' s public token ( ` --calendar-* `
* consumed from ` range-calendar ` ) counts as ` global ` , because the needle is
* this component ' s own directory name — the report flags those as BORROWED
* separately . It over - reports , never under - reports , so a PASS here is a floor .
* The guard that makes this binding lives in ` component-audit ` ( R - 5 ,
* PLAN - theming . md F0 / F3 ) .
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* /
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
import { existsSync , mkdirSync , readdirSync , readFileSync , statSync , writeFileSync } from 'node:fs' ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
import { join , resolve } from 'node:path' ;
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
import { compileMorfo } from '../src/uix/morfo/compile' ;
import type { Morfo } from '../src/uix/morfo/types' ;
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
import { CENSUS_DEBT } from './theming-census-debt.ts' ;
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const ROOT = resolve ( 'src/uix/eidos/components' ) ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const REPORT_DIR = resolve ( 'docs/audit/theming' ) ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const CONTRACT = readFileSync ( resolve ( 'src/uix/eidos/lib/recipes/base.ts' ) , 'utf8' ) . replace (
/\r\n/g ,
'\n'
) ;
const KNOB_PROPS =
uix(anchor-nav): temable — 20 % → 100 %, y el censo aprende los bordes POR LADO
11 claves nuevas (2 → 13). Tenía dos knobs y todo lo demás a pelo: la escala del
índice (que ahora lee el bundle, no `--font-size-sm` suelto), el relleno y el
radio del enlace, sus tres tintas —reposo, hover y activo—, el peso del activo y
LAS DOS TINTAS DEL RAÍL, que es lo que da nombre al componente.
EL RAÍL ACTIVO ESTABA INVISIBLE PARA EL CENSO. Su color vive en un
`border-inline-start-color`, y `KNOB_PROPS` sólo conocía `border-color` y los
shorthands: los ocho longhands por lado (`border-{top,bottom,left,right,
block-start,block-end,inline-start,inline-end}-{color,width}`) no eran knobs para
la medida. El centinela los mira desde el 2026-08-22 —los añadió cuando prose y
nav-tree leyeron muertos—; el censo, no. Corregido: +30 FILAS EN EL CATÁLOGO
(5.101 → 5.131 knobs), y esta ficha baja de 20 % a 18 % antes de coserse, que es
lo honesto: el censo promete sobre-reportar, nunca infra-reportar.
La tinta de reposo del raíl estaba escondida de otra manera: dentro del shorthand
`border-inline-start: var(--anchor-nav-rail-width) solid var(--color-border-subtle)`,
que el censo clasifica por el PRIMER token que casa —el trazo, ya público— y da
la fila por alcanzable. No hizo falta partir el shorthand: basta con nombrar el
color dentro.
Un susto útil: una coma que faltaba en el contrato rompió el build de tokens, y
lo que lo delató fue la SONDA midiendo 0 nodos — la página no renderizaba. El
recuento de nodos es el canario, tal como dice el handoff.
Gates: sonda 0 diffs en 1.760 valores × 7 estados (8 nodos) · centinela 11/12 con
el radio del foco adjudicado (el guard desenfoca a propósito) · censo 100 % ·
`component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 ·
`docs:check` 0 · captura del raíl.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/^(background|background-color|background-image|color|border|border-color|border-width|border-radius|border-block|border-inline|border-top|border-bottom|border-left|border-right|border-block-start|border-block-end|border-inline-start|border-inline-end|border-top-color|border-bottom-color|border-left-color|border-right-color|border-block-start-color|border-block-end-color|border-inline-start-color|border-inline-end-color|border-top-width|border-bottom-width|border-left-width|border-right-width|border-block-start-width|border-block-end-width|border-inline-start-width|border-inline-end-width|box-shadow|outline|outline-color|outline-width|padding|padding-inline|padding-block|padding-top|padding-bottom|padding-left|padding-right|padding-inline-start|padding-inline-end|padding-block-start|padding-block-end|gap|row-gap|column-gap|font-size|font-weight|font-family|line-height|letter-spacing|min-block-size|block-size|min-inline-size|inline-size|height|min-height|width|min-width|opacity|fill|stroke|accent-color|caret-color|text-decoration-color|filter|backdrop-filter)$/ ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/** Transversal systems a recipe CONSUMES (themeable at system level — not per component). */
const SYSTEM =
fix(theming): la aguja del censo dejaba de ver lo que Prettier partia en dos lineas
M0. `classify` decide la clase de cada knob con seis AGUJAS sobre su valor, y
cinco exigian `var(--` con nada entre el parentesis y los guiones. Pero el
valor llega normalizado a espacios simples, asi que una declaracion que
Prettier habia roto en dos lineas llegaba como `var( --x` y la aguja fallaba.
El censo clasificaba por ORTOGRAFIA: el mismo knob puntuaba distinto segun si
su linea cabia o no en 100 columnas.
DOS VICTIMAS en todo el catalogo, una por aguja, medidas y verificadas en el
fichero por el pase adversarial:
- palabras.css:227 (la zebra) — puntuaba `literal`, es `public`. Su hermana
de :220 ya puntuaba public solo por caber en la linea.
- split-button.css:66 (el `min-inline-size` del menu) — puntuaba `global`,
es `channel`: un canal de valor por instancia que el censo llamaba
primitivo crudo. Cumple los tres requisitos de la clase, verificados uno
a uno.
Las otras tres agujas (`SYSTEM`, `STYLE_LAYER` y el `var(--` pelado) llevaban
el MISMO defecto con cero victimas hoy. Se corrigen igual: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa, y esta medido que no
cuesta nada — las cifras salen identicas con y sin ellas (cuatro variantes
construidas y medidas por el adversarial). La tolerancia esta COPIADA, no
inventada: `recipe-css-contract.test.ts:150`, el instrumento hermano de esta
misma casa sobre este mismo constructo, ya la tenia.
LEDGER: dos filas quedan STALE y se retiran con acta en la CABECERA (dentro
del mapa las borraria la proxima regeneracion). NO es deuda perdonada: ninguna
receta cambio, se corrigio una misclasificacion en su origen. Verificado por
cuatro vias independientes, la ultima byte a byte contra lo que el generador
escribiria, que no se borra ni una fila de deuda VIVA.
SUELO: `minAtHundred` 66 -> 67, y el acta dice la verdad sobre el mecanismo
porque el pase adversarial cazo que no la decia. NO es un re-base — en un
re-base se mueve la PREGUNTA y una multitud se mueve con ella sin haber
avanzado (14->45 al corregir el criterio; 45->65 cuando 252 knobs salieron del
denominador). Aqui no se mueve ni el criterio ni la doctrina, pero el
DENOMINADOR SI se mueve, por exactamente un knob: split-button cruza al 100 %
porque su knob no alcanzado SALE del ratio (`global` -> `channel`, excluida por
diseno), no porque ninguno se volviera publico. Lo que gana el alza de todos
modos es que su receta YA era correcta y el censo puntuaba ese trabajo bueno
como primitivo crudo. Misma lectura que la entrada de `tabs`, un piso mas
arriba. `reachPct` deliberadamente INTACTO: la otra victima mueve el ratio
72,7273 % -> 72,7682 %, que sigue siendo 73 — un suelo que subiera con eso
estaria citando ruido.
CIFRAS, predichas -> medidas: public 3072 -> 3073 · literal 325 -> 324 ·
channel 41 -> 42 · global 761 -> 760 · themeable 4224 -> 4223 · atHundred
66 -> 67 · ledger 1152 -> 1150 registered, 0 NEW, 0 STALE. Sobre 5187 knobs
escaneados cambian de clase EXACTAMENTE dos, y son los dos anunciados: cero
sobre-captura, medido por sonda independiente. Suite `theming-reach-floor`
verde; scope `src/uix/eidos` verde.
ANOTADO, no tocado (pre-existente, no de este lote): `scripts/theming-census.ts`
ya venia sucio de Prettier en HEAD — probado formateando la version intacta de
HEAD y obteniendo los mismos hunks desplazados +38, que es el delta de este
commit. Y `scripts/component-audit.ts:1137` arrastra el MISMO defecto de aguja
(cero victimas hoy). Las fichas generadas de split-button y palabras quedan
desfasadas hasta la proxima corrida de `--report`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/var\(\s*--(state-(hover|press|selected)|focus-ring[a-z-]*|depth-[a-z-]+|motion-[a-z-]+|duration-[a-z-]+|ease-[a-z-]+|z-index-[a-z-]+|opacity-[a-z-]+|shape-[a-z-]+|floating-gap[a-z-]*|ring-inset-[a-z-]+)\b/ ;
/** ANY `var(` reference — the presence-only form of the sibling needle (see `classify`). */
const ANY_VAR = /var\(\s*--/ ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const INERT = new Set ( [
'0' ,
'none' ,
'auto' ,
'inherit' ,
'initial' ,
'unset' ,
'transparent' ,
'currentColor' ,
'currentcolor' ,
'revert' ,
'revert-layer'
] ) ;
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception`
Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda
resultan ser desviaciones que el canon ya había firmado.
`recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */`
EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre
(`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el
único instrumento que no: su `strip()` blanquea los comentarios ANTES de
clasificar, así que las **81 anotaciones del catálogo** contaban como literales
sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la
del que no sabía leerlo.
Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como
`system`, y por la misma razón: no es que un tema no lo alcance, es que el canon
ya aceptó por escrito que ahí no va un token.
**54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que
su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human`
16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**),
`result` 1 (86 % → **90 %**), y siete más con una cada uno.
Detalles que costaron medición:
- **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final. Se comprueban
todas las líneas que abarca la declaración.
- **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los
saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea
y basta con guardar el crudo al lado.
- Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a
`literal` (21 → 20) y el conteo vuelve al restaurarla.
Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que
las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna
parte; ahora la ficha del componente las enseña, que es donde se decide si una
sigue siendo cierta.
Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de
reparto); el README de la auditoría gana la fila de la clase y la nota de que
`system` y `exception` quedan fuera del denominador.
Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check
0 · `check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/** recipe-contract §3's exception valve, as written in the canon. */
const ANNOTATED = new RegExp ( '[/][*][ ]*literal:' ) ;
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/ * *
* The SAME valve , one class over : ` /* private: <reason> * / ` ( signature of
* 2026 - 08 - 25 , F2 of the 318 - privates adjudication ) .
*
* ` literal: ` signs a value that is not a token . ` private: ` signs a knob whose
* value routes through a private the doctrine of this axis already blesses but
* that no MECHANICAL test can see , because the blessed shape sits ONE LEVEL
* BELOW the knob : a variant / role CONMUTADOR whose branches are the THM - 2
* palette bridge ( ` card ` , ` avatar ` , ` badge ` , ` surface ` , ` switch ` , ` timeline ` ) ,
* a per - instance value CHANNEL read through an alias ( ` drawer ` , ` popover ` ,
* ` avatar ` ' s custom ring ) , a shared LAYER adopted through a private
* ( ` listbox ` → ` list-surface ` ) , or the identity of a variant ( ` toolbar ` ' s
* ghost bar , ` accordion ` ' s shadowless outline ) .
*
* Read in the same TWO places ` literal: ` is : on the private ' s own declaration
* ( the economical shape — one note adjudicates every knob that reads it ) and
* on the knob declaration itself . A knob is signed when its own declaration
* carries the note OR when EVERY private it reads carries it : ` every ` , not
* ` some ` , for the same reason the bridge test uses it — the unsigned half is
* exactly what still needs adjudicating . A private is signed ONCE , on the
* declaration that establishes the mechanism : a conmutador is ONE decision ,
* not one per branch , and eight identical notes down a per - role cascade would
* be noise pretending to be eight acts .
*
* PLACEMENT differs from ` literal: ` in one way , and it is a prettier
* constraint , not a doctrinal one : the note may also sit in the comment block
* immediately ABOVE the declaration . These reasons cite a sheet and a date , so
* a trailing comment pushes the line past the 100 - column print width and
* prettier answers by breaking the ` var(…) ` across lines — which would make an
* annotation commit rewrite the CSS it only meant to sign . The upward walk
* stops at the first line that carries actual CSS ( tested on the STRIPPED
* body , where a comment - only line is blank ) , so a note can never leak onto the
* declaration that follows the one it was written for .
*
* Resulting class : ` exception ` . The vocabulary stays CLOSED — this is the
* canon ' s existing « signed deviation » class , not a new tier — and , like every
* exception , it leaves the ratio ' s denominator without leaving the listing .
* /
const ANNOTATED_PRIVATE = new RegExp ( '[/][*][ ]*private:' ) ;
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
export type KnobClass =
| 'public'
| 'private'
| 'system'
| 'global'
| 'literal'
| 'exception'
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
| 'structural'
| 'bridge'
| 'channel' ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
export interface Knob {
file : string ;
line : number ;
selector : string ;
prop : string ;
value : string ;
klass : KnobClass ;
}
/** A `--_{c}-x: …` declaration found in the component's own CSS. */
export interface PrivateDecl {
name : string ;
file : string ;
line : number ;
selector : string ;
value : string ;
/** Where the private's own value comes from — the same five classes. */
source : KnobClass ;
}
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
export interface CensusRow {
component : string ;
knobs : number ;
public : number ;
private : number ;
system : number ;
global : number ;
literal : number ;
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception`
Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda
resultan ser desviaciones que el canon ya había firmado.
`recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */`
EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre
(`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el
único instrumento que no: su `strip()` blanquea los comentarios ANTES de
clasificar, así que las **81 anotaciones del catálogo** contaban como literales
sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la
del que no sabía leerlo.
Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como
`system`, y por la misma razón: no es que un tema no lo alcance, es que el canon
ya aceptó por escrito que ahí no va un token.
**54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que
su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human`
16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**),
`result` 1 (86 % → **90 %**), y siete más con una cada uno.
Detalles que costaron medición:
- **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final. Se comprueban
todas las líneas que abarca la declaración.
- **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los
saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea
y basta con guardar el crudo al lado.
- Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a
`literal` (21 → 20) y el conteo vuelve al restaurarla.
Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que
las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna
parte; ahora la ficha del componente las enseña, que es donde se decide si una
sigue siendo cierta.
Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de
reparto); el README de la auditoría gana la fila de la clase y la nota de que
`system` y `exception` quedan fuera del denominador.
Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check
0 · `check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/** A literal carrying its `/* literal: … * /` annotation — recipe-contract §3. */
exception : number ;
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/** Knobs of a component whose 0 % is its nature — `STRUCTURAL_COMPONENTS`. */
structural : number ;
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/** Knobs reading the private the THM-2 palette forward writes — `PALETTE_BRIDGE`. */
bridge : number ;
/** Knobs reading a private nobody declares: soma / the wrapper writes it per instance. */
channel : number ;
/** public / (public + private + global + literal) — the other five out. */
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
reach : number ;
contractKeys : number ;
hasSize : boolean ;
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/ * *
* Comments are blanked , not deleted , so ` file:line ` stays exact and the scanned
* text keeps the same offsets as the source .
* /
function strip ( css : string ) : string {
return css . replace ( /\r\n/g , '\n' ) . replace ( /\/\*[\s\S]*?\*\//g , ( m ) = > m . replace ( /[^\n]/g , ' ' ) ) ;
}
feat(census): los primitivos tipográficos se miden contra su capa — pieza 0 de F2-A
D-TH.2-b, la firma que va ANTES del bloque porque cambia el suelo del censo:
medirla a mitad de camino falsearía todos los antes/después.
`heading` marcaba «36 knobs, 0 % alcanzable» y eso no era deuda: era una
lectura equivocada. Su receta resuelve cada eje como
`var(--_heading-font-size, var(--style-h2-font-size))` — el privado es el
escape POR INSTANCIA que el wrapper escribe desde una prop, y el named style es
la superficie de tema, ya pública y viva. Acuñar `--heading-*` para espejarla
sería un alias por eje × nivel: la clase que mató la purga del changelog §39.
El criterio de qué ES un primitivo tipográfico se midió, no se supuso: o la
receta SELECCIONA por `data-style` (`heading`, `text`, `s-text` — el named
style es su API) o está atada entera a UN style (`code` → `--style-code-*`,
`display` → `--style-hero-*`, `label` → `--style-label-*`). `code-block` queda
FUERA a propósito: lee un par de tokens de style para su texto pero posee
cromo de caja real, y eso sí es suyo.
Detalle que costó una vuelta: la comprobación va ANTES de la de privado. Leer
el privado primero puntúa el primitivo entero como inalcanzable cuando es
completamente temable — por la capa que lo posee.
antes 5.199 knobs · 1.837 públicos (37 %) · 894 privados · 232 sistema
después 5.199 knobs · 1.837 públicos (38 %) · 788 privados · 338 sistema
**Gate**: 106 knobs pasan de `private` a `system` en los SEIS primitivos y
**cero** de los otros 156 se mueve — verificado componente a componente contra
la versión en HEAD, no a ojo. Alcance `—` (nada que poseer) para heading, text
y display; `s-text` al 100 %; `code` y `label` bajan a 7 y 2 knobs de deuda
real.
Informe regenerado (171 veredictos a mano intactos), veredicto de heading
anotado como ejecutado, y recipe-contract §1 gana el párrafo que fija el
criterio — incluida la frontera: un menú que lee `--style-label-font-family`
para una etiqueta NO es un primitivo y no entra en el conjunto.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/ * *
* Typographic primitives ( D - TH . 2 , signed 2026 - 08 - 20 — option ( b ) of the
* ` heading ` sheet ) . Their theming surface IS the named - style layer , so
* ` --style-* ` counts as a transversal SYSTEM for them , never as a global that
* they should have minted a token for . Minting ` --heading-* ` would be one
* alias per axis × level over a layer that is already public and live — the
* class of alias the changelog § 39 purge killed .
*
* The criterion is measured , not guessed : either the recipe SELECTS by
* ` data-style ` ( ` heading ` , ` text ` , ` s-text ` — the named style is their API )
* or it is bound wholesale to ONE named style ( ` code ` → ` --style-code-* ` ,
* ` display ` → ` --style-hero-* ` , ` label ` → ` --style-label-* ` ) .
*
* ` code-block ` is deliberately NOT here : it reads a couple of style tokens for
* its text but owns real box chrome ( border , padding , surface ) , and that IS
* its to own .
* /
const TYPOGRAPHIC_PRIMITIVES = new Set ( [ 'heading' , 'text' , 's-text' , 'code' , 'display' , 'label' ] ) ;
fix(theming): la aguja del censo dejaba de ver lo que Prettier partia en dos lineas
M0. `classify` decide la clase de cada knob con seis AGUJAS sobre su valor, y
cinco exigian `var(--` con nada entre el parentesis y los guiones. Pero el
valor llega normalizado a espacios simples, asi que una declaracion que
Prettier habia roto en dos lineas llegaba como `var( --x` y la aguja fallaba.
El censo clasificaba por ORTOGRAFIA: el mismo knob puntuaba distinto segun si
su linea cabia o no en 100 columnas.
DOS VICTIMAS en todo el catalogo, una por aguja, medidas y verificadas en el
fichero por el pase adversarial:
- palabras.css:227 (la zebra) — puntuaba `literal`, es `public`. Su hermana
de :220 ya puntuaba public solo por caber en la linea.
- split-button.css:66 (el `min-inline-size` del menu) — puntuaba `global`,
es `channel`: un canal de valor por instancia que el censo llamaba
primitivo crudo. Cumple los tres requisitos de la clase, verificados uno
a uno.
Las otras tres agujas (`SYSTEM`, `STYLE_LAYER` y el `var(--` pelado) llevaban
el MISMO defecto con cero victimas hoy. Se corrigen igual: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa, y esta medido que no
cuesta nada — las cifras salen identicas con y sin ellas (cuatro variantes
construidas y medidas por el adversarial). La tolerancia esta COPIADA, no
inventada: `recipe-css-contract.test.ts:150`, el instrumento hermano de esta
misma casa sobre este mismo constructo, ya la tenia.
LEDGER: dos filas quedan STALE y se retiran con acta en la CABECERA (dentro
del mapa las borraria la proxima regeneracion). NO es deuda perdonada: ninguna
receta cambio, se corrigio una misclasificacion en su origen. Verificado por
cuatro vias independientes, la ultima byte a byte contra lo que el generador
escribiria, que no se borra ni una fila de deuda VIVA.
SUELO: `minAtHundred` 66 -> 67, y el acta dice la verdad sobre el mecanismo
porque el pase adversarial cazo que no la decia. NO es un re-base — en un
re-base se mueve la PREGUNTA y una multitud se mueve con ella sin haber
avanzado (14->45 al corregir el criterio; 45->65 cuando 252 knobs salieron del
denominador). Aqui no se mueve ni el criterio ni la doctrina, pero el
DENOMINADOR SI se mueve, por exactamente un knob: split-button cruza al 100 %
porque su knob no alcanzado SALE del ratio (`global` -> `channel`, excluida por
diseno), no porque ninguno se volviera publico. Lo que gana el alza de todos
modos es que su receta YA era correcta y el censo puntuaba ese trabajo bueno
como primitivo crudo. Misma lectura que la entrada de `tabs`, un piso mas
arriba. `reachPct` deliberadamente INTACTO: la otra victima mueve el ratio
72,7273 % -> 72,7682 %, que sigue siendo 73 — un suelo que subiera con eso
estaria citando ruido.
CIFRAS, predichas -> medidas: public 3072 -> 3073 · literal 325 -> 324 ·
channel 41 -> 42 · global 761 -> 760 · themeable 4224 -> 4223 · atHundred
66 -> 67 · ledger 1152 -> 1150 registered, 0 NEW, 0 STALE. Sobre 5187 knobs
escaneados cambian de clase EXACTAMENTE dos, y son los dos anunciados: cero
sobre-captura, medido por sonda independiente. Suite `theming-reach-floor`
verde; scope `src/uix/eidos` verde.
ANOTADO, no tocado (pre-existente, no de este lote): `scripts/theming-census.ts`
ya venia sucio de Prettier en HEAD — probado formateando la version intacta de
HEAD y obteniendo los mismos hunks desplazados +38, que es el delta de este
commit. Y `scripts/component-audit.ts:1137` arrastra el MISMO defecto de aguja
(cero victimas hoy). Las fichas generadas de split-button y palabras quedan
desfasadas hasta la proxima corrida de `--report`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const STYLE_LAYER = /var\(\s*--style-[a-z0-9-]+/ ;
feat(census): los primitivos tipográficos se miden contra su capa — pieza 0 de F2-A
D-TH.2-b, la firma que va ANTES del bloque porque cambia el suelo del censo:
medirla a mitad de camino falsearía todos los antes/después.
`heading` marcaba «36 knobs, 0 % alcanzable» y eso no era deuda: era una
lectura equivocada. Su receta resuelve cada eje como
`var(--_heading-font-size, var(--style-h2-font-size))` — el privado es el
escape POR INSTANCIA que el wrapper escribe desde una prop, y el named style es
la superficie de tema, ya pública y viva. Acuñar `--heading-*` para espejarla
sería un alias por eje × nivel: la clase que mató la purga del changelog §39.
El criterio de qué ES un primitivo tipográfico se midió, no se supuso: o la
receta SELECCIONA por `data-style` (`heading`, `text`, `s-text` — el named
style es su API) o está atada entera a UN style (`code` → `--style-code-*`,
`display` → `--style-hero-*`, `label` → `--style-label-*`). `code-block` queda
FUERA a propósito: lee un par de tokens de style para su texto pero posee
cromo de caja real, y eso sí es suyo.
Detalle que costó una vuelta: la comprobación va ANTES de la de privado. Leer
el privado primero puntúa el primitivo entero como inalcanzable cuando es
completamente temable — por la capa que lo posee.
antes 5.199 knobs · 1.837 públicos (37 %) · 894 privados · 232 sistema
después 5.199 knobs · 1.837 públicos (38 %) · 788 privados · 338 sistema
**Gate**: 106 knobs pasan de `private` a `system` en los SEIS primitivos y
**cero** de los otros 156 se mueve — verificado componente a componente contra
la versión en HEAD, no a ojo. Alcance `—` (nada que poseer) para heading, text
y display; `s-text` al 100 %; `code` y `label` bajan a 7 y 2 knobs de deuda
real.
Informe regenerado (171 veredictos a mano intactos), veredicto de heading
anotado como ejecutado, y recipe-contract §1 gana el párrafo que fija el
criterio — incluida la frontera: un menú que lee `--style-label-font-family`
para una etiqueta NO es un primitivo y no entra en el conjunto.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
feat(calendar-surface): la capa que ya existía, con nombre, casa y su agujero tapado
Firma 1 del acta, diseño presentado y firmado. **La medición desmontó el
encargo**: el handoff la vendía como una capa NUEVA de ~220 knobs, y la capa ya
existía de hecho, sin nombre. `--calendar-*` se emite en `:root` (76 claves) y
sus consumidores no acuñan NADA — medido: `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.
**`lib/calendar-surface.css`** (hook `data-calendar-surface`):
- cuatro coordenadas por talla — `padding`, `control-size`, `day-size`,
`font-size` — xs..lg, porque la familia NO tiene xl, y con la celda DOS pasos
por debajo del bundle de control. Esa desviación estaba escondida en cuatro
bloques `[data-size]` idénticos, uno por receta, cada uno puenteando a un
privado con otro nombre; ahora se lee en un sitio.
- la FORMA del anillo de evento y de la marca de festivo.
**Capa HÍBRIDA, y es lo que la distingue de sus hermanas**: `list-surface` y
`viewport-placement` componen primitivos del sistema, así que declaran sus
públicos en el fichero y no tienen entrada de receta. Ésta no puede: su
vocabulario son 76 claves SEMÁNTICAS que un tema alcanza una a una por config,
así que la entrada `calendar` de `recipes/base.ts` pasa a ser la de la FAMILIA
y la capa posee sólo lo que una entrada de receta no sabe expresar.
**El defecto que la justificaba, medido**: `--calendar-event-shadow` y
`--calendar-day-holiday-shadow` se emitían con ámbito `[data-calendar]`
(audit B.2 los host-scopeó por buenas razones) mientras `range-calendar`,
`month-grid` y `year-grid` los leían desde hosts que nunca llevan ese atributo:
variable VACÍA, `box-shadow` inválido en computed, **el anillo sema de
`commit-select` / `commit-set` no pintaba jamás en tres componentes**. Un token
prestado cuyo ÁMBITO no te cubre no es un préstamo, es un agujero silencioso, y
ningún guard lo veía. Ahora la forma vive en la capa y el acento entra por
`--_calendar-surface-accent`, que cada superficie alimenta con su propio
forward de paleta THM-2.
**Siete wrappers estampan, no cuatro** — y esto casi se me cuela: `DatePicker`
y `DateRangePicker` 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 del root del
picker. Con sólo los cuatro standalone sellando, ambos quedaban con
`--calendar-padding` VACÍA y el panel a padding 0 (medido). 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.
computed 0 diffs en range-calendar (19.285 valores) · month-grid (3.451) ·
year-grid (3.451) · date-picker (464) · date-range-picker (406).
`calendar` da 12, y son del INSTRUMENTO: dos corridas del MISMO
código dan 24 en los mismos nodos y las mismas dos propiedades.
Los «missing node» son el propio sello entrando en la clave.
**Una incidencia nueva, medida y NO arreglada aquí** (§13): el font-size de los
selectores month/year es una moneda al aire —
`[data-calendar-month-select][data-button]` (0,2,0) empata con
`[data-popover-trigger]:not([data-archetype='field-trigger'])` (0,2,0), la
MISMA regla de popover que dejó muerto el cromo de `gradient-picker`, y gana la
hoja que cargue después: 16px o 14px según la recarga. Arreglarlo fija el píxel
en un lado ⇒ decisión.
De paso, `calendar-select.css` deja de puentear un privado que sólo
`[data-calendar]` declaraba: en range / date-range corría SIEMPRE por el
fallback, clavado a md fuera cual fuera la talla.
**El censo deja de penalizar hacer lo correcto**: `LAYER_VOCABULARY` en
`theming-census.ts`, mismo precedente que D-TH.2-b con `--style-*`. Global
43 % → **45 %**, `calendar` 75 % → **84 %**. `list-surface` NO se registra: sus
consumidores puentean por privados, otra forma, y mueve diez componentes de
golpe. Y aparece el techo de debajo, anotado: a los tres consumidores sólo les
quedan los forwards de paleta THM-2 —que el censo cuenta como `private` en TODO
el catálogo— así que siguen leyendo 0 %.
`recipe-css-contract` aprende que una CAPA también declara públicos (antes sólo
miraba la receta, y una capa que comparte prefijo con un componente la hacía
fallar). Sin debilitarla: un nombre que no declara nadie sigue en rojo.
eidos-lint 0 invalid (calendar 29/8 · range-calendar 41/12 · los grids 23/5) ·
audit --only calendar PASS · vitest eidos 434/435 (el rojo conocido) ·
rtl 0/181 · docs 0/813 · check 0 errores en tocados · prettier: revertido el
reformateo en masa que se coló en cuatro README, el test y el censo (churn
ajeno, no mío)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/ * *
* The vocabulary a SHARED LAYER publishes , and who consumes it ( D - TH . 2 - b
* precedent , extended 2026 - 08 - 21 with the ` calendar-surface ` design ) .
*
* Same argument as the typographic primitives : when the layer owns an axis ,
* the consumer ' s theming surface for that axis IS the layer , so reading
* ` --{layer-vocabulary}-* ` is reaching a live public surface — not a raw
* global the component should have minted a token for . Minting one would be
* the parallel vocabulary the layer doctrine forbids , AND it would outrank the
* layer for every other consumer .
*
* Until this existed the census PENALISED doing the correct thing :
* ` range-calendar ` , ` month-grid ` and ` year-grid ` mint zero tokens and read
* 114 / 77 / 77 references from ` --calendar-* ` — measured — and all three
* scored 0 % reach .
*
* DELIBERATELY not registered yet : ` list-surface ` . Its consumers bridge
* through PRIVATES ( ` --_listbox-item-*: var(--list-item-*) ` ) , which is a
* different shape than reading the layer directly , and flipping it moves ten
* components at once — a separate decision ( next - features § 13 ) .
* /
const LAYER_VOCABULARY : { layer : string ; consumers : Set < string > ; needle : RegExp } [ ] = [
{
layer : 'calendar-surface' ,
consumers : new Set ( [ 'range-calendar' , 'month-grid' , 'year-grid' , 'date-picker' ] ) ,
needle : /var\(\s*--calendar-[a-z0-9-]+/
}
] ;
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/ * *
* A component whose 0 % is its NATURE , not its debt ( next - features § 13 ) .
*
* Same shape as ` LAYER_VOCABULARY ` one floor down — the list AND the reason per
* component , so the contract lives IN the artifact instead of in a session ' s
* memory . The class is per COMPONENT , all of its knobs : what was measured is
* the component , not one declaration .
*
* Measured one by one on 2026 - 08 - 22 and SIGNED in the § 5 verdict of each sheet
* ( ` docs/audit/theming/{c}.md ` ) : the five read 0 % reach and NONE of them has a
* contract to write . While the census counted them like a component carrying
* real debt , the global figure lied downwards and F3 ' s gate ( « census 100 % » )
* was unreachable BY CONSTRUCTION .
*
* Their knobs are still counted and still listed — a structural knob is a knob
* — they only leave the ratio ' s DENOMINATOR , exactly like ` system ` does .
*
* This is NOT a drawer for « this one is hard » : every entry is a written § 5
* verdict , and a component that gains a real theme surface leaves the list .
* /
const STRUCTURAL_COMPONENTS = new Map < string , string > ( [
// Its only global is `var(--box-width, 100%)`, BORROWED from `box` (the sheet
// already marks it ⤴): `aspect-ratio` selects `[data-box][data-aspect-ratio]`,
// and `--aspect-ratio` is a per-instance value channel, not a theme surface.
[
'aspect-ratio' ,
'es una FACETA de `box`, no un componente: el eje lo posee `box` y su knob viene prestado (⤴); `--aspect-ratio` es canal de valor por instancia'
] ,
// The `1px` × 2 of the canonical sr-only technique on `[data-text-blur-sr]`.
[
'text-blur' ,
'sus dos knobs son el `1px` de la técnica sr-only: receta de accesibilidad idéntica en todo el catálogo, no estética'
] ,
// The reveal gate's `opacity` — motion-channel mechanics.
[
'cascade' ,
'su knob es el `opacity` del gate antiparpadeo: mecánica del canal de motion, cuyo valor tematizable vive en `EidosConfig.motion`'
] ,
[
'motion' ,
'igual que `cascade`: el `opacity` del gate `[data-animation-pending]` es mecánica del canal, tematizable desde `EidosConfig.motion`'
] ,
// `max-content` × 2 on the popover that hosts the calendar: a composition fix
// with ONE correct value. Its chrome comes from `field` / `date-field`.
[
'date-picker' ,
'sus dos knobs son la corrección `max-content` del pie del popover — un único valor correcto; su cromo vive en `field`, `calendar` y `picker-shell`'
]
] ) ;
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/ * *
* The two shapes of private this axis ' doctrine already adjudicated as NOT
* debt — read off the EMITTER ' S OUTPUT , never guessed from the name .
*
* The anchor is ` src/uix/eidos/generated/base.css ` : ` generated-css.test.ts `
* keeps it byte - identical to ` renderStaticCss() ` , and ` recipe-css-contract `
* already reads it to assert that every palette recipe emits its forward . So
* the question asked here is « what does the generator WRITE » , not « what does
* this name look like » — and that difference is the whole point : adjudicating
* by name is the blindness this same axis was burned by .
*
* ` bridge ` — the THM - 2 forward ( ` renderRecipePaletteForward ` +
* ` isPaletteSlotToken ` , render - css . ts ) writes ` --_{c}-palette-{slot} ` under
* the presence guard ` [data-{c}]:where([data-color], [data-color-custom]) ` ,
* with the component ' s OWN host default as the fallback . A recipe reading it
* consumes the MECHANISM of the per - instance palette , and it is reachable
* TWICE : through the shared ` --palette-* ` layer and through the component ' s
* own tone tokens ( ` --card-neutral-track ` ) — both public . A public on top
* would let a theme PIN it and kill the ` color= ` of every instance in
* silence ; that is written , measured , in the § 5 verdicts of ` card ` ,
* ` tags-input ` and ` avatar ` .
* The test is the forward LINE , not a name shaped like one : only
* ` --_{c}-palette-{slot}: var(--palette-{slot}, …) ` counts , so a
* ` --_c-palette-shadow ` nobody forwards stays residue ( same discipline as
* ` isPaletteSlotToken ` , which matches the slot const and not a loose regex ) .
*
* ` channel ` — the value channel : a private the recipe READS and NOBODY
* declares , neither the component ' s CSS nor the generator . Soma or the
* wrapper writes it per INSTANCE ( ` --_progress-value-pct ` ,
* ` --_dialog-content-width-override ` , ` --_chronos-event-accent ` ) . A theme
* must not reach it : pinning it breaks the behaviour , which is why ` tabs `
* refused its § 4.1 proposal in writing . Measured 2026 - 08 - 25 : 28 of the 29
* names in this class have a writer in a wrapper ( ` *.svelte ` ) or in soma ;
* the 29 th ( ` --_scroll-frames-viewport-block-size ` ) is the consumer hatch
* its contract signs as SITE geometry — its writer is the page , by design .
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
*
* Both leave the ratio ' s DENOMINATOR , exactly like ` system ` and ` structural ` ;
* their knobs are still counted and still listed . A private that is NEITHER
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* stays ` private ` : the RESIDUE that signature deliberately left to be
* adjudicated key by key ( ` --residue ` ) . That adjudication landed on
* 2026 - 08 - 25 : of its 150 knobs , 84 carry a written ` /* private: <reason> * / `
* ( class ` exception ` ) because the shape the doctrine blesses sits one level
* BELOW the knob , and the other 66 are registered in the debt ledger . ` private `
* is a ` DebtClass ` from that day , so a new one arrives NAMED .
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* /
const GENERATED_CSS = readFileSync ( resolve ( 'src/uix/eidos/generated/base.css' ) , 'utf8' ) . replace (
/\r\n/g ,
'\n'
) ;
/** `--_{c}-palette-{slot}` AS THE FORWARD EMITS IT — the bridge, verbatim. */
const PALETTE_BRIDGE = new Set (
[
. . . GENERATED_CSS . matchAll (
/(--_[a-z0-9-]+-palette-([a-z-]+))\s*:\s*var\(\s*--palette-\2\s*[,)]/g
)
] . map ( ( m ) = > m [ 1 ] )
) ;
/ * *
* Every private the GENERATOR declares — recipe keys , palette forwards and the
* gradient - finish ramp ( ` --_{c}-fill-finish ` ) . A private in here is written by
* the build , so it is NOT a runtime channel however undeclared the CSS leaves
* it : without this test the four gradient - finish knobs read as « written by
* soma » , which is the wrong reason for the right answer .
* /
const GENERATED_PRIVATES = new Set (
[ . . . GENERATED_CSS . matchAll ( /(?<=^\s*|;\s*)(--_[a-z0-9-]+)\s*:/gm ) ] . map ( ( m ) = > m [ 1 ] )
) ;
fix(theming): la aguja del censo dejaba de ver lo que Prettier partia en dos lineas
M0. `classify` decide la clase de cada knob con seis AGUJAS sobre su valor, y
cinco exigian `var(--` con nada entre el parentesis y los guiones. Pero el
valor llega normalizado a espacios simples, asi que una declaracion que
Prettier habia roto en dos lineas llegaba como `var( --x` y la aguja fallaba.
El censo clasificaba por ORTOGRAFIA: el mismo knob puntuaba distinto segun si
su linea cabia o no en 100 columnas.
DOS VICTIMAS en todo el catalogo, una por aguja, medidas y verificadas en el
fichero por el pase adversarial:
- palabras.css:227 (la zebra) — puntuaba `literal`, es `public`. Su hermana
de :220 ya puntuaba public solo por caber en la linea.
- split-button.css:66 (el `min-inline-size` del menu) — puntuaba `global`,
es `channel`: un canal de valor por instancia que el censo llamaba
primitivo crudo. Cumple los tres requisitos de la clase, verificados uno
a uno.
Las otras tres agujas (`SYSTEM`, `STYLE_LAYER` y el `var(--` pelado) llevaban
el MISMO defecto con cero victimas hoy. Se corrigen igual: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa, y esta medido que no
cuesta nada — las cifras salen identicas con y sin ellas (cuatro variantes
construidas y medidas por el adversarial). La tolerancia esta COPIADA, no
inventada: `recipe-css-contract.test.ts:150`, el instrumento hermano de esta
misma casa sobre este mismo constructo, ya la tenia.
LEDGER: dos filas quedan STALE y se retiran con acta en la CABECERA (dentro
del mapa las borraria la proxima regeneracion). NO es deuda perdonada: ninguna
receta cambio, se corrigio una misclasificacion en su origen. Verificado por
cuatro vias independientes, la ultima byte a byte contra lo que el generador
escribiria, que no se borra ni una fila de deuda VIVA.
SUELO: `minAtHundred` 66 -> 67, y el acta dice la verdad sobre el mecanismo
porque el pase adversarial cazo que no la decia. NO es un re-base — en un
re-base se mueve la PREGUNTA y una multitud se mueve con ella sin haber
avanzado (14->45 al corregir el criterio; 45->65 cuando 252 knobs salieron del
denominador). Aqui no se mueve ni el criterio ni la doctrina, pero el
DENOMINADOR SI se mueve, por exactamente un knob: split-button cruza al 100 %
porque su knob no alcanzado SALE del ratio (`global` -> `channel`, excluida por
diseno), no porque ninguno se volviera publico. Lo que gana el alza de todos
modos es que su receta YA era correcta y el censo puntuaba ese trabajo bueno
como primitivo crudo. Misma lectura que la entrada de `tabs`, un piso mas
arriba. `reachPct` deliberadamente INTACTO: la otra victima mueve el ratio
72,7273 % -> 72,7682 %, que sigue siendo 73 — un suelo que subiera con eso
estaria citando ruido.
CIFRAS, predichas -> medidas: public 3072 -> 3073 · literal 325 -> 324 ·
channel 41 -> 42 · global 761 -> 760 · themeable 4224 -> 4223 · atHundred
66 -> 67 · ledger 1152 -> 1150 registered, 0 NEW, 0 STALE. Sobre 5187 knobs
escaneados cambian de clase EXACTAMENTE dos, y son los dos anunciados: cero
sobre-captura, medido por sonda independiente. Suite `theming-reach-floor`
verde; scope `src/uix/eidos` verde.
ANOTADO, no tocado (pre-existente, no de este lote): `scripts/theming-census.ts`
ya venia sucio de Prettier en HEAD — probado formateando la version intacta de
HEAD y obteniendo los mismos hunks desplazados +38, que es el delta de este
commit. Y `scripts/component-audit.ts:1137` arrastra el MISMO defecto de aguja
(cero victimas hoy). Las fichas generadas de split-button y palabras quedan
desfasadas hasta la proxima corrida de `--report`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/ * *
* ACTA 2026 - 08 - 27 ( M0 ) — the needle used to read its own input wrong .
*
* Every test below is a NEEDLE over the knob ' s value , and five of the six
* demanded ` var(-- ` with nothing between the paren and the dashes . But the value
* reaching here is normalized to single spaces ( ` val ` below ) , so a declaration
* PRETTIER broke over two lines arrives as ` var( --x ` — and the needle missed .
* The census was classifying by SPELLING : the same knob scored differently
* depending on whether its line happened to fit in 100 columns .
*
* Measured , two victims in the whole catalogue , one per needle :
* · ` palabras/palabras.css ` zebra stripe — ` literal ` , is ` public ` ;
* · ` split-button/split-button.css ` menu ` min-inline-size ` — ` global ` , is
* ` channel ` ( a per - instance value channel the census called a raw primitive ) .
* ` STYLE_LAYER ` , ` SYSTEM ` and the bare ` var(-- ` carried the SAME defect with no
* victim today ; they are corrected with the other two because leaving them blind
* leaves the bug armed for the next line prettier decides to break , and because
* the measurement says it costs nothing ( figures identical with and without ) .
*
* The tolerance is COPIED , not invented : ` recipe-css-contract.test.ts:150 ` — the
* sibling instrument in this same house , over this same construct — already
* reads ` var \ ( \ s*(--[a-z][a-z0-9-]+) \ s*[,)] ` . Same reason the ref regexes here
* ( ` /var \ ( \ s*(--_…)/g ` ) were always tolerant ; only the ` String.includes ` needles
* were left behind .
*
* Nothing about the CLASSIFICATION changes — the order and the meaning of the
* six tests are untouched . What changes is that each needle now sees what it
* always meant to see .
*
* NOT global ( ` /…/g ` ) : a ` g ` regex carries ` lastIndex ` across ` .test() ` calls
* and would answer differently on alternate invocations of the same needle .
* /
feat(census): los primitivos tipográficos se miden contra su capa — pieza 0 de F2-A
D-TH.2-b, la firma que va ANTES del bloque porque cambia el suelo del censo:
medirla a mitad de camino falsearía todos los antes/después.
`heading` marcaba «36 knobs, 0 % alcanzable» y eso no era deuda: era una
lectura equivocada. Su receta resuelve cada eje como
`var(--_heading-font-size, var(--style-h2-font-size))` — el privado es el
escape POR INSTANCIA que el wrapper escribe desde una prop, y el named style es
la superficie de tema, ya pública y viva. Acuñar `--heading-*` para espejarla
sería un alias por eje × nivel: la clase que mató la purga del changelog §39.
El criterio de qué ES un primitivo tipográfico se midió, no se supuso: o la
receta SELECCIONA por `data-style` (`heading`, `text`, `s-text` — el named
style es su API) o está atada entera a UN style (`code` → `--style-code-*`,
`display` → `--style-hero-*`, `label` → `--style-label-*`). `code-block` queda
FUERA a propósito: lee un par de tokens de style para su texto pero posee
cromo de caja real, y eso sí es suyo.
Detalle que costó una vuelta: la comprobación va ANTES de la de privado. Leer
el privado primero puntúa el primitivo entero como inalcanzable cuando es
completamente temable — por la capa que lo posee.
antes 5.199 knobs · 1.837 públicos (37 %) · 894 privados · 232 sistema
después 5.199 knobs · 1.837 públicos (38 %) · 788 privados · 338 sistema
**Gate**: 106 knobs pasan de `private` a `system` en los SEIS primitivos y
**cero** de los otros 156 se mueve — verificado componente a componente contra
la versión en HEAD, no a ojo. Alcance `—` (nada que poseer) para heading, text
y display; `s-text` al 100 %; `code` y `label` bajan a 7 y 2 knobs de deuda
real.
Informe regenerado (171 veredictos a mano intactos), veredicto de heading
anotado como ejecutado, y recipe-contract §1 gana el párrafo que fija el
criterio — incluida la frontera: un menú que lee `--style-label-font-family`
para una etiqueta NO es un primitivo y no entra en el conjunto.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
function classify (
value : string ,
fix(theming): la aguja del censo dejaba de ver lo que Prettier partia en dos lineas
M0. `classify` decide la clase de cada knob con seis AGUJAS sobre su valor, y
cinco exigian `var(--` con nada entre el parentesis y los guiones. Pero el
valor llega normalizado a espacios simples, asi que una declaracion que
Prettier habia roto en dos lineas llegaba como `var( --x` y la aguja fallaba.
El censo clasificaba por ORTOGRAFIA: el mismo knob puntuaba distinto segun si
su linea cabia o no en 100 columnas.
DOS VICTIMAS en todo el catalogo, una por aguja, medidas y verificadas en el
fichero por el pase adversarial:
- palabras.css:227 (la zebra) — puntuaba `literal`, es `public`. Su hermana
de :220 ya puntuaba public solo por caber en la linea.
- split-button.css:66 (el `min-inline-size` del menu) — puntuaba `global`,
es `channel`: un canal de valor por instancia que el censo llamaba
primitivo crudo. Cumple los tres requisitos de la clase, verificados uno
a uno.
Las otras tres agujas (`SYSTEM`, `STYLE_LAYER` y el `var(--` pelado) llevaban
el MISMO defecto con cero victimas hoy. Se corrigen igual: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa, y esta medido que no
cuesta nada — las cifras salen identicas con y sin ellas (cuatro variantes
construidas y medidas por el adversarial). La tolerancia esta COPIADA, no
inventada: `recipe-css-contract.test.ts:150`, el instrumento hermano de esta
misma casa sobre este mismo constructo, ya la tenia.
LEDGER: dos filas quedan STALE y se retiran con acta en la CABECERA (dentro
del mapa las borraria la proxima regeneracion). NO es deuda perdonada: ninguna
receta cambio, se corrigio una misclasificacion en su origen. Verificado por
cuatro vias independientes, la ultima byte a byte contra lo que el generador
escribiria, que no se borra ni una fila de deuda VIVA.
SUELO: `minAtHundred` 66 -> 67, y el acta dice la verdad sobre el mecanismo
porque el pase adversarial cazo que no la decia. NO es un re-base — en un
re-base se mueve la PREGUNTA y una multitud se mueve con ella sin haber
avanzado (14->45 al corregir el criterio; 45->65 cuando 252 knobs salieron del
denominador). Aqui no se mueve ni el criterio ni la doctrina, pero el
DENOMINADOR SI se mueve, por exactamente un knob: split-button cruza al 100 %
porque su knob no alcanzado SALE del ratio (`global` -> `channel`, excluida por
diseno), no porque ninguno se volviera publico. Lo que gana el alza de todos
modos es que su receta YA era correcta y el censo puntuaba ese trabajo bueno
como primitivo crudo. Misma lectura que la entrada de `tabs`, un piso mas
arriba. `reachPct` deliberadamente INTACTO: la otra victima mueve el ratio
72,7273 % -> 72,7682 %, que sigue siendo 73 — un suelo que subiera con eso
estaria citando ruido.
CIFRAS, predichas -> medidas: public 3072 -> 3073 · literal 325 -> 324 ·
channel 41 -> 42 · global 761 -> 760 · themeable 4224 -> 4223 · atHundred
66 -> 67 · ledger 1152 -> 1150 registered, 0 NEW, 0 STALE. Sobre 5187 knobs
escaneados cambian de clase EXACTAMENTE dos, y son los dos anunciados: cero
sobre-captura, medido por sonda independiente. Suite `theming-reach-floor`
verde; scope `src/uix/eidos` verde.
ANOTADO, no tocado (pre-existente, no de este lote): `scripts/theming-census.ts`
ya venia sucio de Prettier en HEAD — probado formateando la version intacta de
HEAD y obteniendo los mismos hunks desplazados +38, que es el delta de este
commit. Y `scripts/component-audit.ts:1137` arrastra el MISMO defecto de aguja
(cero victimas hoy). Las fichas generadas de split-button y palabras quedan
desfasadas hasta la proxima corrida de `--report`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
pubNeedle : RegExp ,
privNeedle : RegExp ,
feat(census): los primitivos tipográficos se miden contra su capa — pieza 0 de F2-A
D-TH.2-b, la firma que va ANTES del bloque porque cambia el suelo del censo:
medirla a mitad de camino falsearía todos los antes/después.
`heading` marcaba «36 knobs, 0 % alcanzable» y eso no era deuda: era una
lectura equivocada. Su receta resuelve cada eje como
`var(--_heading-font-size, var(--style-h2-font-size))` — el privado es el
escape POR INSTANCIA que el wrapper escribe desde una prop, y el named style es
la superficie de tema, ya pública y viva. Acuñar `--heading-*` para espejarla
sería un alias por eje × nivel: la clase que mató la purga del changelog §39.
El criterio de qué ES un primitivo tipográfico se midió, no se supuso: o la
receta SELECCIONA por `data-style` (`heading`, `text`, `s-text` — el named
style es su API) o está atada entera a UN style (`code` → `--style-code-*`,
`display` → `--style-hero-*`, `label` → `--style-label-*`). `code-block` queda
FUERA a propósito: lee un par de tokens de style para su texto pero posee
cromo de caja real, y eso sí es suyo.
Detalle que costó una vuelta: la comprobación va ANTES de la de privado. Leer
el privado primero puntúa el primitivo entero como inalcanzable cuando es
completamente temable — por la capa que lo posee.
antes 5.199 knobs · 1.837 públicos (37 %) · 894 privados · 232 sistema
después 5.199 knobs · 1.837 públicos (38 %) · 788 privados · 338 sistema
**Gate**: 106 knobs pasan de `private` a `system` en los SEIS primitivos y
**cero** de los otros 156 se mueve — verificado componente a componente contra
la versión en HEAD, no a ojo. Alcance `—` (nada que poseer) para heading, text
y display; `s-text` al 100 %; `code` y `label` bajan a 7 y 2 knobs de deuda
real.
Informe regenerado (171 veredictos a mano intactos), veredicto de heading
anotado como ejecutado, y recipe-contract §1 gana el párrafo que fija el
criterio — incluida la frontera: un menú que lee `--style-label-font-family`
para una etiqueta NO es un primitivo y no entra en el conjunto.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
component? : string
) : KnobClass {
fix(theming): la aguja del censo dejaba de ver lo que Prettier partia en dos lineas
M0. `classify` decide la clase de cada knob con seis AGUJAS sobre su valor, y
cinco exigian `var(--` con nada entre el parentesis y los guiones. Pero el
valor llega normalizado a espacios simples, asi que una declaracion que
Prettier habia roto en dos lineas llegaba como `var( --x` y la aguja fallaba.
El censo clasificaba por ORTOGRAFIA: el mismo knob puntuaba distinto segun si
su linea cabia o no en 100 columnas.
DOS VICTIMAS en todo el catalogo, una por aguja, medidas y verificadas en el
fichero por el pase adversarial:
- palabras.css:227 (la zebra) — puntuaba `literal`, es `public`. Su hermana
de :220 ya puntuaba public solo por caber en la linea.
- split-button.css:66 (el `min-inline-size` del menu) — puntuaba `global`,
es `channel`: un canal de valor por instancia que el censo llamaba
primitivo crudo. Cumple los tres requisitos de la clase, verificados uno
a uno.
Las otras tres agujas (`SYSTEM`, `STYLE_LAYER` y el `var(--` pelado) llevaban
el MISMO defecto con cero victimas hoy. Se corrigen igual: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa, y esta medido que no
cuesta nada — las cifras salen identicas con y sin ellas (cuatro variantes
construidas y medidas por el adversarial). La tolerancia esta COPIADA, no
inventada: `recipe-css-contract.test.ts:150`, el instrumento hermano de esta
misma casa sobre este mismo constructo, ya la tenia.
LEDGER: dos filas quedan STALE y se retiran con acta en la CABECERA (dentro
del mapa las borraria la proxima regeneracion). NO es deuda perdonada: ninguna
receta cambio, se corrigio una misclasificacion en su origen. Verificado por
cuatro vias independientes, la ultima byte a byte contra lo que el generador
escribiria, que no se borra ni una fila de deuda VIVA.
SUELO: `minAtHundred` 66 -> 67, y el acta dice la verdad sobre el mecanismo
porque el pase adversarial cazo que no la decia. NO es un re-base — en un
re-base se mueve la PREGUNTA y una multitud se mueve con ella sin haber
avanzado (14->45 al corregir el criterio; 45->65 cuando 252 knobs salieron del
denominador). Aqui no se mueve ni el criterio ni la doctrina, pero el
DENOMINADOR SI se mueve, por exactamente un knob: split-button cruza al 100 %
porque su knob no alcanzado SALE del ratio (`global` -> `channel`, excluida por
diseno), no porque ninguno se volviera publico. Lo que gana el alza de todos
modos es que su receta YA era correcta y el censo puntuaba ese trabajo bueno
como primitivo crudo. Misma lectura que la entrada de `tabs`, un piso mas
arriba. `reachPct` deliberadamente INTACTO: la otra victima mueve el ratio
72,7273 % -> 72,7682 %, que sigue siendo 73 — un suelo que subiera con eso
estaria citando ruido.
CIFRAS, predichas -> medidas: public 3072 -> 3073 · literal 325 -> 324 ·
channel 41 -> 42 · global 761 -> 760 · themeable 4224 -> 4223 · atHundred
66 -> 67 · ledger 1152 -> 1150 registered, 0 NEW, 0 STALE. Sobre 5187 knobs
escaneados cambian de clase EXACTAMENTE dos, y son los dos anunciados: cero
sobre-captura, medido por sonda independiente. Suite `theming-reach-floor`
verde; scope `src/uix/eidos` verde.
ANOTADO, no tocado (pre-existente, no de este lote): `scripts/theming-census.ts`
ya venia sucio de Prettier en HEAD — probado formateando la version intacta de
HEAD y obteniendo los mismos hunks desplazados +38, que es el delta de este
commit. Y `scripts/component-audit.ts:1137` arrastra el MISMO defecto de aguja
(cero victimas hoy). Las fichas generadas de split-button y palabras quedan
desfasadas hasta la proxima corrida de `--report`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
if ( pubNeedle . test ( value ) ) return 'public' ;
feat(census): los primitivos tipográficos se miden contra su capa — pieza 0 de F2-A
D-TH.2-b, la firma que va ANTES del bloque porque cambia el suelo del censo:
medirla a mitad de camino falsearía todos los antes/después.
`heading` marcaba «36 knobs, 0 % alcanzable» y eso no era deuda: era una
lectura equivocada. Su receta resuelve cada eje como
`var(--_heading-font-size, var(--style-h2-font-size))` — el privado es el
escape POR INSTANCIA que el wrapper escribe desde una prop, y el named style es
la superficie de tema, ya pública y viva. Acuñar `--heading-*` para espejarla
sería un alias por eje × nivel: la clase que mató la purga del changelog §39.
El criterio de qué ES un primitivo tipográfico se midió, no se supuso: o la
receta SELECCIONA por `data-style` (`heading`, `text`, `s-text` — el named
style es su API) o está atada entera a UN style (`code` → `--style-code-*`,
`display` → `--style-hero-*`, `label` → `--style-label-*`). `code-block` queda
FUERA a propósito: lee un par de tokens de style para su texto pero posee
cromo de caja real, y eso sí es suyo.
Detalle que costó una vuelta: la comprobación va ANTES de la de privado. Leer
el privado primero puntúa el primitivo entero como inalcanzable cuando es
completamente temable — por la capa que lo posee.
antes 5.199 knobs · 1.837 públicos (37 %) · 894 privados · 232 sistema
después 5.199 knobs · 1.837 públicos (38 %) · 788 privados · 338 sistema
**Gate**: 106 knobs pasan de `private` a `system` en los SEIS primitivos y
**cero** de los otros 156 se mueve — verificado componente a componente contra
la versión en HEAD, no a ojo. Alcance `—` (nada que poseer) para heading, text
y display; `s-text` al 100 %; `code` y `label` bajan a 7 y 2 knobs de deuda
real.
Informe regenerado (171 veredictos a mano intactos), veredicto de heading
anotado como ejecutado, y recipe-contract §1 gana el párrafo que fija el
criterio — incluida la frontera: un menú que lee `--style-label-font-family`
para una etiqueta NO es un primitivo y no entra en el conjunto.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// BEFORE the private check, and on purpose: a typographic primitive writes
// `var(--_heading-font-size, var(--style-h2-font-size))`, where the private
// is the per-INSTANCE escape hatch the wrapper sets from a prop and the
// named style is the THEMING surface. Reading the private first would score
// the whole primitive as unreachable when it is, in fact, fully themeable —
// through the layer that owns it.
if ( component && TYPOGRAPHIC_PRIMITIVES . has ( component ) && STYLE_LAYER . test ( value ) )
return 'system' ;
feat(calendar-surface): la capa que ya existía, con nombre, casa y su agujero tapado
Firma 1 del acta, diseño presentado y firmado. **La medición desmontó el
encargo**: el handoff la vendía como una capa NUEVA de ~220 knobs, y la capa ya
existía de hecho, sin nombre. `--calendar-*` se emite en `:root` (76 claves) y
sus consumidores no acuñan NADA — medido: `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.
**`lib/calendar-surface.css`** (hook `data-calendar-surface`):
- cuatro coordenadas por talla — `padding`, `control-size`, `day-size`,
`font-size` — xs..lg, porque la familia NO tiene xl, y con la celda DOS pasos
por debajo del bundle de control. Esa desviación estaba escondida en cuatro
bloques `[data-size]` idénticos, uno por receta, cada uno puenteando a un
privado con otro nombre; ahora se lee en un sitio.
- la FORMA del anillo de evento y de la marca de festivo.
**Capa HÍBRIDA, y es lo que la distingue de sus hermanas**: `list-surface` y
`viewport-placement` componen primitivos del sistema, así que declaran sus
públicos en el fichero y no tienen entrada de receta. Ésta no puede: su
vocabulario son 76 claves SEMÁNTICAS que un tema alcanza una a una por config,
así que la entrada `calendar` de `recipes/base.ts` pasa a ser la de la FAMILIA
y la capa posee sólo lo que una entrada de receta no sabe expresar.
**El defecto que la justificaba, medido**: `--calendar-event-shadow` y
`--calendar-day-holiday-shadow` se emitían con ámbito `[data-calendar]`
(audit B.2 los host-scopeó por buenas razones) mientras `range-calendar`,
`month-grid` y `year-grid` los leían desde hosts que nunca llevan ese atributo:
variable VACÍA, `box-shadow` inválido en computed, **el anillo sema de
`commit-select` / `commit-set` no pintaba jamás en tres componentes**. Un token
prestado cuyo ÁMBITO no te cubre no es un préstamo, es un agujero silencioso, y
ningún guard lo veía. Ahora la forma vive en la capa y el acento entra por
`--_calendar-surface-accent`, que cada superficie alimenta con su propio
forward de paleta THM-2.
**Siete wrappers estampan, no cuatro** — y esto casi se me cuela: `DatePicker`
y `DateRangePicker` 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 del root del
picker. Con sólo los cuatro standalone sellando, ambos quedaban con
`--calendar-padding` VACÍA y el panel a padding 0 (medido). 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.
computed 0 diffs en range-calendar (19.285 valores) · month-grid (3.451) ·
year-grid (3.451) · date-picker (464) · date-range-picker (406).
`calendar` da 12, y son del INSTRUMENTO: dos corridas del MISMO
código dan 24 en los mismos nodos y las mismas dos propiedades.
Los «missing node» son el propio sello entrando en la clave.
**Una incidencia nueva, medida y NO arreglada aquí** (§13): el font-size de los
selectores month/year es una moneda al aire —
`[data-calendar-month-select][data-button]` (0,2,0) empata con
`[data-popover-trigger]:not([data-archetype='field-trigger'])` (0,2,0), la
MISMA regla de popover que dejó muerto el cromo de `gradient-picker`, y gana la
hoja que cargue después: 16px o 14px según la recarga. Arreglarlo fija el píxel
en un lado ⇒ decisión.
De paso, `calendar-select.css` deja de puentear un privado que sólo
`[data-calendar]` declaraba: en range / date-range corría SIEMPRE por el
fallback, clavado a md fuera cual fuera la talla.
**El censo deja de penalizar hacer lo correcto**: `LAYER_VOCABULARY` en
`theming-census.ts`, mismo precedente que D-TH.2-b con `--style-*`. Global
43 % → **45 %**, `calendar` 75 % → **84 %**. `list-surface` NO se registra: sus
consumidores puentean por privados, otra forma, y mueve diez componentes de
golpe. Y aparece el techo de debajo, anotado: a los tres consumidores sólo les
quedan los forwards de paleta THM-2 —que el censo cuenta como `private` en TODO
el catálogo— así que siguen leyendo 0 %.
`recipe-css-contract` aprende que una CAPA también declara públicos (antes sólo
miraba la receta, y una capa que comparte prefijo con un componente la hacía
fallar). Sin debilitarla: un nombre que no declara nadie sigue en rojo.
eidos-lint 0 invalid (calendar 29/8 · range-calendar 41/12 · los grids 23/5) ·
audit --only calendar PASS · vitest eidos 434/435 (el rojo conocido) ·
rtl 0/181 · docs 0/813 · check 0 errores en tocados · prettier: revertido el
reformateo en masa que se coló en cuatro README, el test y el censo (churn
ajeno, no mío)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Same reasoning, one layer up: a shared layer's vocabulary read by one of
// its declared consumers IS that consumer's theming surface for the axis.
if ( component && LAYER_VOCABULARY . some ( ( l ) = > l . consumers . has ( component ) && l . needle . test ( value ) ) )
return 'system' ;
fix(theming): la aguja del censo dejaba de ver lo que Prettier partia en dos lineas
M0. `classify` decide la clase de cada knob con seis AGUJAS sobre su valor, y
cinco exigian `var(--` con nada entre el parentesis y los guiones. Pero el
valor llega normalizado a espacios simples, asi que una declaracion que
Prettier habia roto en dos lineas llegaba como `var( --x` y la aguja fallaba.
El censo clasificaba por ORTOGRAFIA: el mismo knob puntuaba distinto segun si
su linea cabia o no en 100 columnas.
DOS VICTIMAS en todo el catalogo, una por aguja, medidas y verificadas en el
fichero por el pase adversarial:
- palabras.css:227 (la zebra) — puntuaba `literal`, es `public`. Su hermana
de :220 ya puntuaba public solo por caber en la linea.
- split-button.css:66 (el `min-inline-size` del menu) — puntuaba `global`,
es `channel`: un canal de valor por instancia que el censo llamaba
primitivo crudo. Cumple los tres requisitos de la clase, verificados uno
a uno.
Las otras tres agujas (`SYSTEM`, `STYLE_LAYER` y el `var(--` pelado) llevaban
el MISMO defecto con cero victimas hoy. Se corrigen igual: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa, y esta medido que no
cuesta nada — las cifras salen identicas con y sin ellas (cuatro variantes
construidas y medidas por el adversarial). La tolerancia esta COPIADA, no
inventada: `recipe-css-contract.test.ts:150`, el instrumento hermano de esta
misma casa sobre este mismo constructo, ya la tenia.
LEDGER: dos filas quedan STALE y se retiran con acta en la CABECERA (dentro
del mapa las borraria la proxima regeneracion). NO es deuda perdonada: ninguna
receta cambio, se corrigio una misclasificacion en su origen. Verificado por
cuatro vias independientes, la ultima byte a byte contra lo que el generador
escribiria, que no se borra ni una fila de deuda VIVA.
SUELO: `minAtHundred` 66 -> 67, y el acta dice la verdad sobre el mecanismo
porque el pase adversarial cazo que no la decia. NO es un re-base — en un
re-base se mueve la PREGUNTA y una multitud se mueve con ella sin haber
avanzado (14->45 al corregir el criterio; 45->65 cuando 252 knobs salieron del
denominador). Aqui no se mueve ni el criterio ni la doctrina, pero el
DENOMINADOR SI se mueve, por exactamente un knob: split-button cruza al 100 %
porque su knob no alcanzado SALE del ratio (`global` -> `channel`, excluida por
diseno), no porque ninguno se volviera publico. Lo que gana el alza de todos
modos es que su receta YA era correcta y el censo puntuaba ese trabajo bueno
como primitivo crudo. Misma lectura que la entrada de `tabs`, un piso mas
arriba. `reachPct` deliberadamente INTACTO: la otra victima mueve el ratio
72,7273 % -> 72,7682 %, que sigue siendo 73 — un suelo que subiera con eso
estaria citando ruido.
CIFRAS, predichas -> medidas: public 3072 -> 3073 · literal 325 -> 324 ·
channel 41 -> 42 · global 761 -> 760 · themeable 4224 -> 4223 · atHundred
66 -> 67 · ledger 1152 -> 1150 registered, 0 NEW, 0 STALE. Sobre 5187 knobs
escaneados cambian de clase EXACTAMENTE dos, y son los dos anunciados: cero
sobre-captura, medido por sonda independiente. Suite `theming-reach-floor`
verde; scope `src/uix/eidos` verde.
ANOTADO, no tocado (pre-existente, no de este lote): `scripts/theming-census.ts`
ya venia sucio de Prettier en HEAD — probado formateando la version intacta de
HEAD y obteniendo los mismos hunks desplazados +38, que es el delta de este
commit. Y `scripts/component-audit.ts:1137` arrastra el MISMO defecto de aguja
(cero victimas hoy). Las fichas generadas de split-button y palabras quedan
desfasadas hasta la proxima corrida de `--report`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
if ( privNeedle . test ( value ) ) return 'private' ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( SYSTEM . test ( value ) ) return 'system' ;
fix(theming): la aguja del censo dejaba de ver lo que Prettier partia en dos lineas
M0. `classify` decide la clase de cada knob con seis AGUJAS sobre su valor, y
cinco exigian `var(--` con nada entre el parentesis y los guiones. Pero el
valor llega normalizado a espacios simples, asi que una declaracion que
Prettier habia roto en dos lineas llegaba como `var( --x` y la aguja fallaba.
El censo clasificaba por ORTOGRAFIA: el mismo knob puntuaba distinto segun si
su linea cabia o no en 100 columnas.
DOS VICTIMAS en todo el catalogo, una por aguja, medidas y verificadas en el
fichero por el pase adversarial:
- palabras.css:227 (la zebra) — puntuaba `literal`, es `public`. Su hermana
de :220 ya puntuaba public solo por caber en la linea.
- split-button.css:66 (el `min-inline-size` del menu) — puntuaba `global`,
es `channel`: un canal de valor por instancia que el censo llamaba
primitivo crudo. Cumple los tres requisitos de la clase, verificados uno
a uno.
Las otras tres agujas (`SYSTEM`, `STYLE_LAYER` y el `var(--` pelado) llevaban
el MISMO defecto con cero victimas hoy. Se corrigen igual: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa, y esta medido que no
cuesta nada — las cifras salen identicas con y sin ellas (cuatro variantes
construidas y medidas por el adversarial). La tolerancia esta COPIADA, no
inventada: `recipe-css-contract.test.ts:150`, el instrumento hermano de esta
misma casa sobre este mismo constructo, ya la tenia.
LEDGER: dos filas quedan STALE y se retiran con acta en la CABECERA (dentro
del mapa las borraria la proxima regeneracion). NO es deuda perdonada: ninguna
receta cambio, se corrigio una misclasificacion en su origen. Verificado por
cuatro vias independientes, la ultima byte a byte contra lo que el generador
escribiria, que no se borra ni una fila de deuda VIVA.
SUELO: `minAtHundred` 66 -> 67, y el acta dice la verdad sobre el mecanismo
porque el pase adversarial cazo que no la decia. NO es un re-base — en un
re-base se mueve la PREGUNTA y una multitud se mueve con ella sin haber
avanzado (14->45 al corregir el criterio; 45->65 cuando 252 knobs salieron del
denominador). Aqui no se mueve ni el criterio ni la doctrina, pero el
DENOMINADOR SI se mueve, por exactamente un knob: split-button cruza al 100 %
porque su knob no alcanzado SALE del ratio (`global` -> `channel`, excluida por
diseno), no porque ninguno se volviera publico. Lo que gana el alza de todos
modos es que su receta YA era correcta y el censo puntuaba ese trabajo bueno
como primitivo crudo. Misma lectura que la entrada de `tabs`, un piso mas
arriba. `reachPct` deliberadamente INTACTO: la otra victima mueve el ratio
72,7273 % -> 72,7682 %, que sigue siendo 73 — un suelo que subiera con eso
estaria citando ruido.
CIFRAS, predichas -> medidas: public 3072 -> 3073 · literal 325 -> 324 ·
channel 41 -> 42 · global 761 -> 760 · themeable 4224 -> 4223 · atHundred
66 -> 67 · ledger 1152 -> 1150 registered, 0 NEW, 0 STALE. Sobre 5187 knobs
escaneados cambian de clase EXACTAMENTE dos, y son los dos anunciados: cero
sobre-captura, medido por sonda independiente. Suite `theming-reach-floor`
verde; scope `src/uix/eidos` verde.
ANOTADO, no tocado (pre-existente, no de este lote): `scripts/theming-census.ts`
ya venia sucio de Prettier en HEAD — probado formateando la version intacta de
HEAD y obteniendo los mismos hunks desplazados +38, que es el delta de este
commit. Y `scripts/component-audit.ts:1137` arrastra el MISMO defecto de aguja
(cero victimas hoy). Las fichas generadas de split-button y palabras quedan
desfasadas hasta la proxima corrida de `--report`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
if ( ANY_VAR . test ( value ) ) return 'global' ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
return 'literal' ;
}
function recipeBlock ( c : string ) : string | null {
uix(avatar): temable — 75 % → 90 %, y su contrato era INVISIBLE para los dos instrumentos
Protocolo PLAN-theming §7 sobre `avatar` (incluye `AvatarGroup`, que comparte
receta y fichero). Censo 75 % → **90 %**, contrato 84 → 88 claves, centinela
**83/88** con 5 adjudicadas. El default NO se mueve.
## Lo primero: el instrumento no veía el contrato
La entrada `avatar` de `recipes/base.ts` es la ÚNICA construida por una IIFE (un
helper local genera sus 24 ámbitos compuestos), así que su mapa vive en el
`return {`, un tabulador más adentro. Consecuencia medida:
- el censo la leía como «sin entrada en base.ts» — 84 claves invisibles;
- el centinela **moría** con `no recipe block for avatar`: el componente no se
podía medir, y su gate no significaba nada.
Los dos lectores leen ya la IIFE (dedentan el `return`). Sin este arreglo, todo
lo demás de este commit habría sido humo.
## Lo que entra al contrato (6 claves)
`group-overlap-{xs,sm,md,lg,xl,xxl}` — la escala de solape de `AvatarGroup`.
Había UNA clave, `--avatar-group-overlap`, y la receta la RE-DECLARABA en seis
bloques `[data-size]`: sentada en el elemento, ganaba siempre al `:root` donde
escribe un tema. Medido: `37px` desde el asiento del tema no movía el margen; el
mismo valor escrito sobre el nodo, sí. Ahora el paso viaja por
`--_avatar-group-overlap` y los seis alcanzan desde `:root` (−8,4 · −11,2 · −14 ·
−16,8 · −22,4 · −33,6 px → 37 px, uno a uno).
## Lo que se retira (2 declaraciones muertas, diff 0 las dos)
- `group-max`: el envoltorio escribía `--avatar-group-max` INLINE y la receta
declaraba su default `99`. **No lo leía nadie** — el tope se aplica con
`data-has-max` + `:nth-child(n + M)` porque una variable no entra en
`:nth-child()`, y lo dice el propio comentario del CSS. Retirado de los dos
sitios; el `+3` del grupo sigue exactamente donde estaba.
- el respaldo `, white` de `--_avatar-badge-fg`: el contrato ya declara
`--avatar-badge-color-custom-contrast: white`, así que era inalcanzable y sólo
podía envejecer contra su token. Comprobado en la rama custom: la tinta sigue
computando `rgb(255, 255, 255)`.
## Seis literales firmados (salen del ratio)
Los cuatro `100 %` de Image y Fallback son IDENTIDAD — la parte ES la superficie
del avatar, no una talla propia — y los dos `line-height: 1` mantienen el glifo
centrado por la caja flex.
## Lo que se queda privado (el techo real es 90 %)
`--_avatar-bg`, `--_avatar-fg` y `--_avatar-badge-bg` son un CONMUTADOR: cambian
de fuente con la variante (solid · soft · outline) y su valor sale del forward de
paleta THM-2 que la capa de color alimenta por instancia desde `[data-color]`.
Un público encima dejaría que un tema los fijara y matara el `color=` de cada
avatar.
## Artefactos
- Sonda: **0 diffs** en TRES bases — demo de avatar con insignia y anillo (576
valores), la misma en modo fallback (384) y demo de AvatarGroup (3.072).
- Capturas 2× antes/después de las dos demos: **idénticas al byte**.
- Centinela R-5.4: 83/88, 5 adjudicadas con su medida en el ledger (`size-xxl` y
`font-size-xxl` viven en el paso `xxl`, que el barrido del guard no alcanza —
el límite que `metrics` ya registró; `radius-none` y `ring-width-sm` son «sólo
el paso en vigor pinta»; `group-overlap-xxl` junta las dos cosas).
- El guard aprende `avatar`: la insignia y el anillo son OPT-IN, el barrido de
`data-variant` destapa las 16 claves `*-outline-*`, y `AvatarGroup` se mide en
su propia ruta (21 muertos → 5).
- `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido
`skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check`
COMPLETED sin un solo error en `avatar`.
- README «Talla y tema» + pestaña `Tokens` (88 filas, verificada en el navegador
con `networkidle` y clic real).
## Lo que NO se arregla aquí (anotado)
- **R-5.3 preexistente**: cuatro claves `*-color-custom*` donde la gramática
exige `fg`. Son escotillas que el envoltorio escribe INLINE, así que
renombrarlas cambia el contrato público de tres canales: se lista, el audit
sigue en NEEDS-WORK por esto y sólo por esto.
- **Falso POSITIVO nuevo del centinela** (next-features §13): escribe el token
en `:root` y también sobre cada nodo, y para una propiedad personalizada que
la receta re-declara en el ELEMENTO ese inline gana justo lo que un tema
pierde. Es lo que hacía que `--avatar-group-overlap` leyera vivo.
- **El barrido de tallas del guard para en `xl`**: añadir `xxl` dejaría STALE
las seis excepciones de `metrics`, así que se hace con la re-verificación del
ledger entero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// `avatar` is the ONLY entry whose value is an IIFE (a local matrix helper
// generates its 24 composite scopes), so its map lives in the `return {` one
// tab deeper and this reader saw NO contract for it — 84 keys reported as
// «sin entrada en base.ts». Read the returned map and dedent it once; the
// key regexes below work unchanged.
const iife = CONTRACT . indexOf ( '\n\t' + c + ': ((): RecipeTokenMap => {' ) ;
if ( iife >= 0 ) {
const from = CONTRACT . indexOf ( '\n\t\treturn {' , iife ) ;
return CONTRACT . slice ( from , CONTRACT . indexOf ( '\n\t\t};' , from ) ) . replace ( /^\t/gm , '' ) ;
}
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const a = CONTRACT . indexOf ( "\n\t'" + c + "': {" ) ;
const b = CONTRACT . indexOf ( '\n\t' + c + ': {' ) ;
const start = a >= 0 ? a : b ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( start < 0 ) return null ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const end = CONTRACT . indexOf ( '\n\t},' , start ) ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
return CONTRACT . slice ( start , end < 0 ? undefined : end ) ;
}
function contractKeysFor ( c : string ) : number {
const block = recipeBlock ( c ) ;
if ( block === null ) return 0 ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
return ( block . match ( /^\t\t'?[a-z0-9-]+'?\s*:/gm ) ? ? [ ] ) . length ;
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/** Every top-level key of the recipe block — public AND `_private` forwards. */
function contractKeyNames ( c : string ) : string [ ] {
const block = recipeBlock ( c ) ;
if ( block === null ) return [ ] ;
return [ . . . block . matchAll ( /^\t\t'?(_?[a-z0-9-]+)'?\s*:/gm ) ] . map ( ( m ) = > m [ 1 ] ) ;
}
export interface Scan {
row : CensusRow ;
knobs : Knob [ ] ;
privates : PrivateDecl [ ] ;
/** Privates the CSS consumes but never declares — from `base.ts` or an inline style. */
privatesFromContract : string [ ] ;
files : string [ ] ;
}
/** Every component directory — used to spot a token BORROWED from another recipe. */
const COMPONENT_DIRS = readdirSync ( ROOT ) . filter ( ( d ) = > statSync ( join ( ROOT , d ) ) . isDirectory ( ) ) ;
export function scanComponent ( dir : string ) : Scan | null {
const d = join ( ROOT , dir ) ;
if ( ! statSync ( d ) . isDirectory ( ) ) return null ;
const files = readdirSync ( d ) . filter ( ( f ) = > f . endsWith ( '.css' ) ) ;
if ( files . length === 0 ) return null ;
fix(theming): la aguja del censo dejaba de ver lo que Prettier partia en dos lineas
M0. `classify` decide la clase de cada knob con seis AGUJAS sobre su valor, y
cinco exigian `var(--` con nada entre el parentesis y los guiones. Pero el
valor llega normalizado a espacios simples, asi que una declaracion que
Prettier habia roto en dos lineas llegaba como `var( --x` y la aguja fallaba.
El censo clasificaba por ORTOGRAFIA: el mismo knob puntuaba distinto segun si
su linea cabia o no en 100 columnas.
DOS VICTIMAS en todo el catalogo, una por aguja, medidas y verificadas en el
fichero por el pase adversarial:
- palabras.css:227 (la zebra) — puntuaba `literal`, es `public`. Su hermana
de :220 ya puntuaba public solo por caber en la linea.
- split-button.css:66 (el `min-inline-size` del menu) — puntuaba `global`,
es `channel`: un canal de valor por instancia que el censo llamaba
primitivo crudo. Cumple los tres requisitos de la clase, verificados uno
a uno.
Las otras tres agujas (`SYSTEM`, `STYLE_LAYER` y el `var(--` pelado) llevaban
el MISMO defecto con cero victimas hoy. Se corrigen igual: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa, y esta medido que no
cuesta nada — las cifras salen identicas con y sin ellas (cuatro variantes
construidas y medidas por el adversarial). La tolerancia esta COPIADA, no
inventada: `recipe-css-contract.test.ts:150`, el instrumento hermano de esta
misma casa sobre este mismo constructo, ya la tenia.
LEDGER: dos filas quedan STALE y se retiran con acta en la CABECERA (dentro
del mapa las borraria la proxima regeneracion). NO es deuda perdonada: ninguna
receta cambio, se corrigio una misclasificacion en su origen. Verificado por
cuatro vias independientes, la ultima byte a byte contra lo que el generador
escribiria, que no se borra ni una fila de deuda VIVA.
SUELO: `minAtHundred` 66 -> 67, y el acta dice la verdad sobre el mecanismo
porque el pase adversarial cazo que no la decia. NO es un re-base — en un
re-base se mueve la PREGUNTA y una multitud se mueve con ella sin haber
avanzado (14->45 al corregir el criterio; 45->65 cuando 252 knobs salieron del
denominador). Aqui no se mueve ni el criterio ni la doctrina, pero el
DENOMINADOR SI se mueve, por exactamente un knob: split-button cruza al 100 %
porque su knob no alcanzado SALE del ratio (`global` -> `channel`, excluida por
diseno), no porque ninguno se volviera publico. Lo que gana el alza de todos
modos es que su receta YA era correcta y el censo puntuaba ese trabajo bueno
como primitivo crudo. Misma lectura que la entrada de `tabs`, un piso mas
arriba. `reachPct` deliberadamente INTACTO: la otra victima mueve el ratio
72,7273 % -> 72,7682 %, que sigue siendo 73 — un suelo que subiera con eso
estaria citando ruido.
CIFRAS, predichas -> medidas: public 3072 -> 3073 · literal 325 -> 324 ·
channel 41 -> 42 · global 761 -> 760 · themeable 4224 -> 4223 · atHundred
66 -> 67 · ledger 1152 -> 1150 registered, 0 NEW, 0 STALE. Sobre 5187 knobs
escaneados cambian de clase EXACTAMENTE dos, y son los dos anunciados: cero
sobre-captura, medido por sonda independiente. Suite `theming-reach-floor`
verde; scope `src/uix/eidos` verde.
ANOTADO, no tocado (pre-existente, no de este lote): `scripts/theming-census.ts`
ya venia sucio de Prettier en HEAD — probado formateando la version intacta de
HEAD y obteniendo los mismos hunks desplazados +38, que es el delta de este
commit. Y `scripts/component-audit.ts:1137` arrastra el MISMO defecto de aguja
(cero victimas hoy). Las fichas generadas de split-button y palabras quedan
desfasadas hasta la proxima corrida de `--report`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// `\s*` after the paren, not a literal `var(--`: see the ACTA over `classify`.
// Every component directory matches `^[a-z0-9-]+$` (measured over `readdirSync`),
// so the interpolation needs no escaping — `-` is not special outside a class.
const pubNeedle = new RegExp ( 'var\\(\\s*--' + dir + '-' ) ;
const privNeedle = new RegExp ( 'var\\(\\s*--_' + dir + '-' ) ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const row : CensusRow = {
component : dir ,
knobs : 0 ,
public : 0 ,
private : 0 ,
system : 0 ,
global : 0 ,
literal : 0 ,
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception`
Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda
resultan ser desviaciones que el canon ya había firmado.
`recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */`
EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre
(`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el
único instrumento que no: su `strip()` blanquea los comentarios ANTES de
clasificar, así que las **81 anotaciones del catálogo** contaban como literales
sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la
del que no sabía leerlo.
Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como
`system`, y por la misma razón: no es que un tema no lo alcance, es que el canon
ya aceptó por escrito que ahí no va un token.
**54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que
su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human`
16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**),
`result` 1 (86 % → **90 %**), y siete más con una cada uno.
Detalles que costaron medición:
- **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final. Se comprueban
todas las líneas que abarca la declaración.
- **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los
saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea
y basta con guardar el crudo al lado.
- Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a
`literal` (21 → 20) y el conteo vuelve al restaurarla.
Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que
las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna
parte; ahora la ficha del componente las enseña, que es donde se decide si una
sigue siendo cierta.
Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de
reparto); el README de la auditoría gana la fila de la clase y la nota de que
`system` y `exception` quedan fuera del denominador.
Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check
0 · `check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
exception : 0 ,
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
structural : 0 ,
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
bridge : 0 ,
channel : 0 ,
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
reach : 0 ,
contractKeys : contractKeysFor ( dir ) ,
hasSize : false
} ;
const knobs : Knob [ ] = [ ] ;
const privates : PrivateDecl [ ] = [ ] ;
const usedPrivates = new Set < string > ( ) ;
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/** `--_{c}-x` declarations carrying `/* private: … * /` — the valve, one level down. */
const signedPrivates = new Set < string > ( ) ;
/** Knobs carrying the note on their OWN declaration — the valve, at the knob. */
const signedKnobs = new Set < Knob > ( ) ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
for ( const file of files ) {
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception`
Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda
resultan ser desviaciones que el canon ya había firmado.
`recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */`
EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre
(`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el
único instrumento que no: su `strip()` blanquea los comentarios ANTES de
clasificar, así que las **81 anotaciones del catálogo** contaban como literales
sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la
del que no sabía leerlo.
Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como
`system`, y por la misma razón: no es que un tema no lo alcance, es que el canon
ya aceptó por escrito que ahí no va un token.
**54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que
su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human`
16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**),
`result` 1 (86 % → **90 %**), y siete más con una cada uno.
Detalles que costaron medición:
- **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final. Se comprueban
todas las líneas que abarca la declaración.
- **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los
saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea
y basta con guardar el crudo al lado.
- Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a
`literal` (21 → 20) y el conteo vuelve al restaurarla.
Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que
las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna
parte; ahora la ficha del componente las enseña, que es donde se decide si una
sigue siendo cierta.
Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de
reparto); el README de la auditoría gana la fila de la clase y la nota de que
`system` y `exception` quedan fuera del denominador.
Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check
0 · `check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// The stripped body is what gets CLASSIFIED (a comment must not read as a
// value), but the annotation of recipe-contract §3 lives IN a comment, so
// the raw lines are kept beside it. `strip` blanks comments in place, so
// the two are line-for-line aligned.
const raw = readFileSync ( join ( d , file ) , 'utf8' ) . replace ( /\r\n/g , '\n' ) ;
const rawLines = raw . split ( '\n' ) ;
const body = strip ( raw ) ;
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const bodyLines = body . split ( '\n' ) ;
/ * *
* The ` private: ` note on the declaration at ` line ` , or in the comment
* block immediately above it . The walk climbs only over lines that are
* BLANK once comments are stripped — i . e . lines that were nothing but a
* comment — so it stops dead at the previous declaration , at the rule ' s
* ` { ` , and at the selector .
* /
const signedAt = ( line : number , endLine : number ) : boolean = > {
for ( let ln = line ; ln <= endLine ; ln ++ )
if ( ANNOTATED_PRIVATE . test ( rawLines [ ln - 1 ] ? ? '' ) ) return true ;
for ( let ln = line - 1 ; ln >= 1 && ( bodyLines [ ln - 1 ] ? ? '' ) . trim ( ) === '' ; ln -- )
if ( ANNOTATED_PRIVATE . test ( rawLines [ ln - 1 ] ? ? '' ) ) return true ;
return false ;
} ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( body . includes ( 'data-size' ) ) row . hasSize = true ;
const lineStarts : number [ ] = [ 0 ] ;
for ( let i = 0 ; i < body . length ; i ++ ) if ( body . charCodeAt ( i ) === 10 ) lineStarts . push ( i + 1 ) ;
const lineAt = ( idx : number ) = > {
let lo = 0 ;
let hi = lineStarts . length - 1 ;
while ( lo < hi ) {
const mid = ( lo + hi + 1 ) >> 1 ;
if ( lineStarts [ mid ] <= idx ) lo = mid ;
else hi = mid - 1 ;
}
return lo + 1 ;
} ;
// Block-wise so every declaration carries the selector it lives under.
// `[^{}]` cannot cross a brace, so an @media's inner rules are matched
// individually and the at-rule prelude never leaks into the selector.
for ( const block of body . matchAll ( /([^{}]+)\{([^{}]*)\}/g ) ) {
// A statement at-rule (`@import '…';`) sits in the same run as the next
// selector — keep what follows its `;`, or the whole rule is skipped and
// its knobs vanish (measured: color-picker lost 2).
const selector = ( block [ 1 ] . split ( ';' ) . pop ( ) ? ? '' ) . trim ( ) . replace ( /\s+/g , ' ' ) ;
if ( selector . startsWith ( '@' ) ) continue ; // @property / @font-face: no knobs
const inner = block [ 2 ] ;
const innerStart = ( block . index ? ? 0 ) + block [ 1 ] . length + 1 ;
// Not anchored to the line start: a one-line rule (`[x] { padding: 8px; }`)
// counts too — the first version of this regex missed it, and a mutation
// test (PLAN-theming.md §7.5) is what caught it.
for ( const m of inner . matchAll ( /(?<=^\s*|;\s*)(--[a-z0-9_-]+|[a-z-]+)\s*:\s*([^;]+);/gm ) ) {
const prop = m [ 1 ] ;
const val = m [ 2 ] . trim ( ) . replace ( /\s+/g , ' ' ) ;
const line = lineAt ( innerStart + ( m . index ? ? 0 ) ) ;
for ( const p of val . matchAll ( /var\(\s*(--_[a-z0-9-]+)/g ) ) usedPrivates . add ( p [ 1 ] ) ;
if ( prop . startsWith ( '--' ) ) {
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( prop . startsWith ( '--_' + dir + '-' ) ) {
// recipe-contract §3 again, one level down: a private whose value is a
// literal WITH its written reason is a signed absence, not debt. Button
// declares `--_button-bg: transparent` on the variants that ARE the absence
// of chrome; without this those privates score as non-derived and six knobs
// read unreachable while every OTHER branch of the same private reads a
// public. Same valve as the knob below, same requirement: the reason is
// written on the declaration.
let source = classify ( val , pubNeedle , privNeedle , dir ) ;
if ( source === 'literal' && ANNOTATED . test ( rawLines [ line - 1 ] ? ? '' ) )
source = 'exception' ;
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// The `private:` valve. It does NOT touch `source`: a signed private
// must not read as «derives from a public», which would score the
// knob `public` and inflate the reach. It signs the knobs that READ
// it, in the fourth pass below.
if ( signedAt ( line , lineAt ( innerStart + ( m . index ? ? 0 ) + m [ 0 ] . length - 1 ) ) )
signedPrivates . add ( prop ) ;
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
privates . push ( { name : prop , file , line , selector , value : val , source } ) ;
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
continue ;
}
if ( ! KNOB_PROPS . test ( prop ) || INERT . has ( val ) ) continue ;
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// A structural component is structural WHOLE: classifying its knobs one
// by one would ask a question its §5 verdict already answered.
let klass : KnobClass = STRUCTURAL_COMPONENTS . has ( dir )
? 'structural'
: classify ( val , pubNeedle , privNeedle , dir ) ;
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception`
Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda
resultan ser desviaciones que el canon ya había firmado.
`recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */`
EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre
(`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el
único instrumento que no: su `strip()` blanquea los comentarios ANTES de
clasificar, así que las **81 anotaciones del catálogo** contaban como literales
sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la
del que no sabía leerlo.
Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como
`system`, y por la misma razón: no es que un tema no lo alcance, es que el canon
ya aceptó por escrito que ahí no va un token.
**54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que
su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human`
16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**),
`result` 1 (86 % → **90 %**), y siete más con una cada uno.
Detalles que costaron medición:
- **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final. Se comprueban
todas las líneas que abarca la declaración.
- **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los
saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea
y basta con guardar el crudo al lado.
- Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a
`literal` (21 → 20) y el conteo vuelve al restaurarla.
Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que
las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna
parte; ahora la ficha del componente las enseña, que es donde se decide si una
sigue siendo cierta.
Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de
reparto); el README de la auditoría gana la fila de la clase y la nota de que
`system` y `exception` quedan fuera del denominador.
Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check
0 · `check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// recipe-contract §3: a deviation annotated on its own declaration is
// a SIGNED exception, not drift — the same valve `component-audit`
// honours for R-4.x. The census used to count all 81 of them as debt
// because `strip` blanks the comment before anything is classified.
// The whole declaration counts as "its own line": a value broken over
// several lines carries the note at the end.
if ( klass === 'literal' ) {
const endLine = lineAt ( innerStart + ( m . index ? ? 0 ) + m [ 0 ] . length - 1 ) ;
for ( let ln = line ; ln <= endLine ; ln ++ )
if ( ANNOTATED . test ( rawLines [ ln - 1 ] ? ? '' ) ) {
klass = 'exception' ;
break ;
}
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
row [ klass ] ++ ;
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const knob : Knob = { file , line , selector , prop , value : val , klass } ;
// Same span as the `literal:` note above: a value broken over several
// lines carries its note at the end. Recorded now and applied in the
// fourth pass, because a knob can still be reclassified `public`,
// `bridge` or `channel` first — and where a mechanical test decides,
// a written note has nothing left to sign.
if ( klass === 'private' ) {
const endLine = lineAt ( innerStart + ( m . index ? ? 0 ) + m [ 0 ] . length - 1 ) ;
if ( signedAt ( line , endLine ) ) signedKnobs . add ( knob ) ;
}
knobs . push ( knob ) ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
}
}
}
uix(theming): el censo aprende que un privado DERIVADO sí alcanza — 50 % → 56 %
Seis puntos de alcance sin tocar una línea de componente: **la medición estaba
mal, no el código**.
EL DEFECTO. F0 encargaba «detección "privado que deriva de público" (leer
`--_c-x: var(--c-y)` en la misma receta y reclasificar)». Se implementó a
medias: el censo CALCULA `derives` y lo imprime en la columna «¿deriva de un
público?» de la §3 de cada ficha, pero **el contador nunca lo usó** — esos
knobs seguían puntuando como deuda. Medido: **240 privados en 59 componentes**
estaban en ese estado, es decir, haciendo exactamente lo que la doctrina
prescribe («privados sólo si derivan de públicos») y penalizados por ello.
Lo destapó tokenizar `avatar`: sus 17 privados YA leían públicos
(`--_avatar-size: var(--avatar-size-md)`), así que no había nada que tokenizar
— había que arreglar el instrumento.
LA CORRECCIÓN. Segunda pasada tras recoger privados y knobs: un knob que lee
`var(--_c-x)` pasa a `public` si ese privado alcanza un público. El cierre es
**TRANSITIVO** (`--_a: calc(var(--_b) * .3)` alcanza si `--_b` alcanza) con
conjunto de visitados para que un ciclo no lo cuelgue.
**Muta-prueba** (obligatoria, memoria `a-guard-that-inspects-nothing-passes`):
apuntar `--_avatar-radius` al primitivo crudo baja avatar de 75 % a 72 % y el
knob vuelve a `private`; revertirlo lo devuelve a 75 % y deja el árbol
idéntico.
EFECTO MEDIDO:
- Global **50 % → 56 %** · privados 625 → 341 · al 100 % 10 → 14 · <20 % 22 → 20.
- `avatar` 36 % → **75 %**, `picker-shell` 76 % → **95 %**, `menu-dial` 25 % →
38 %, y otras 51 fichas con cifras nuevas — **ninguna por cambio de código**.
- Los READMEs de `picker-shell` y `menu-dial` se actualizan con la cifra nueva
y con la razón, para que nadie crea que el componente cambió.
⚠ **Cualquier cifra anterior a este commit no es comparable con las de ahora.**
Queda escrito en el handoff.
Y un hueco de F0 que sale al hacerlo, registrado en §13: **el test del SUELO
que el plan pedía NO EXISTE** («alcance global ≥ el de hoy y literales ≤ 614 —
el número sube o el test falla»). Hoy nada impide que el alcance BAJE entre
sesiones: sólo se vería mirando la cifra a mano. Con el censo ya endurecido, es
el momento de escribirlo.
Gates: `component:audit` 162 PASS / 4 NEEDS-WORK (preexistentes) · `--names`
DESVIADAS 0 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · el
censo compila.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
// ── Second pass: a private that DERIVES from a public is reachable ──
// F0 asked for this and it was only ever printed in the report's §3, never
// applied to the count: a recipe that writes `--_c-x: var(--c-y)` and reads
// `var(--_c-x)` is doing exactly what the doctrine prescribes (privates are
// fine IF they derive from a public), yet every such knob scored as debt.
// Measured 2026-08-22: 240 privates across 59 components were in that state.
// The closure is TRANSITIVE — `--_a: calc(var(--_b) * .3)` reaches a public
// whenever `--_b` does — with a visited set so a cycle cannot hang it.
const declBySource = new Map < string , string [ ] > ( ) ;
for ( const pv of privates ) declBySource . set ( pv . name , [ . . . ( declBySource . get ( pv . name ) ? ? [ ] ) , pv . source ] ) ;
const privValues = new Map < string , string [ ] > ( ) ;
for ( const pv of privates ) privValues . set ( pv . name , [ . . . ( privValues . get ( pv . name ) ? ? [ ] ) , pv . value ] ) ;
const derivesCache = new Map < string , boolean > ( ) ;
const derivesFromPublic = ( name : string , seen = new Set < string > ( ) ) : boolean = > {
if ( derivesCache . has ( name ) ) return derivesCache . get ( name ) ! ;
if ( seen . has ( name ) ) return false ;
seen . add ( name ) ;
const sources = declBySource . get ( name ) ;
if ( ! sources || sources . length === 0 ) return false ;
let ok = sources . every ( ( s ) = > {
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( s === 'public' || s === 'exception' ) return true ;
uix(theming): el censo aprende que un privado DERIVADO sí alcanza — 50 % → 56 %
Seis puntos de alcance sin tocar una línea de componente: **la medición estaba
mal, no el código**.
EL DEFECTO. F0 encargaba «detección "privado que deriva de público" (leer
`--_c-x: var(--c-y)` en la misma receta y reclasificar)». Se implementó a
medias: el censo CALCULA `derives` y lo imprime en la columna «¿deriva de un
público?» de la §3 de cada ficha, pero **el contador nunca lo usó** — esos
knobs seguían puntuando como deuda. Medido: **240 privados en 59 componentes**
estaban en ese estado, es decir, haciendo exactamente lo que la doctrina
prescribe («privados sólo si derivan de públicos») y penalizados por ello.
Lo destapó tokenizar `avatar`: sus 17 privados YA leían públicos
(`--_avatar-size: var(--avatar-size-md)`), así que no había nada que tokenizar
— había que arreglar el instrumento.
LA CORRECCIÓN. Segunda pasada tras recoger privados y knobs: un knob que lee
`var(--_c-x)` pasa a `public` si ese privado alcanza un público. El cierre es
**TRANSITIVO** (`--_a: calc(var(--_b) * .3)` alcanza si `--_b` alcanza) con
conjunto de visitados para que un ciclo no lo cuelgue.
**Muta-prueba** (obligatoria, memoria `a-guard-that-inspects-nothing-passes`):
apuntar `--_avatar-radius` al primitivo crudo baja avatar de 75 % a 72 % y el
knob vuelve a `private`; revertirlo lo devuelve a 75 % y deja el árbol
idéntico.
EFECTO MEDIDO:
- Global **50 % → 56 %** · privados 625 → 341 · al 100 % 10 → 14 · <20 % 22 → 20.
- `avatar` 36 % → **75 %**, `picker-shell` 76 % → **95 %**, `menu-dial` 25 % →
38 %, y otras 51 fichas con cifras nuevas — **ninguna por cambio de código**.
- Los READMEs de `picker-shell` y `menu-dial` se actualizan con la cifra nueva
y con la razón, para que nadie crea que el componente cambió.
⚠ **Cualquier cifra anterior a este commit no es comparable con las de ahora.**
Queda escrito en el handoff.
Y un hueco de F0 que sale al hacerlo, registrado en §13: **el test del SUELO
que el plan pedía NO EXISTE** («alcance global ≥ el de hoy y literales ≤ 614 —
el número sube o el test falla»). Hoy nada impide que el alcance BAJE entre
sesiones: sólo se vería mirando la cifra a mano. Con el censo ya endurecido, es
el momento de escribirlo.
Gates: `component:audit` 162 PASS / 4 NEEDS-WORK (preexistentes) · `--names`
DESVIADAS 0 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · el
censo compila.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
if ( s !== 'private' ) return false ;
return true ; // resolved below, per referenced private
} ) ;
if ( ok && sources . some ( ( s ) = > s === 'private' ) ) {
// every private this one reads must itself reach a public
ok = ( privValues . get ( name ) ? ? [ ] ) . every ( ( v ) = > {
const refs = [ . . . v . matchAll ( /var\(\s*(--_[a-z0-9-]+)/g ) ] . map ( ( m ) = > m [ 1 ] ) ;
fix(theming): la aguja del censo dejaba de ver lo que Prettier partia en dos lineas
M0. `classify` decide la clase de cada knob con seis AGUJAS sobre su valor, y
cinco exigian `var(--` con nada entre el parentesis y los guiones. Pero el
valor llega normalizado a espacios simples, asi que una declaracion que
Prettier habia roto en dos lineas llegaba como `var( --x` y la aguja fallaba.
El censo clasificaba por ORTOGRAFIA: el mismo knob puntuaba distinto segun si
su linea cabia o no en 100 columnas.
DOS VICTIMAS en todo el catalogo, una por aguja, medidas y verificadas en el
fichero por el pase adversarial:
- palabras.css:227 (la zebra) — puntuaba `literal`, es `public`. Su hermana
de :220 ya puntuaba public solo por caber en la linea.
- split-button.css:66 (el `min-inline-size` del menu) — puntuaba `global`,
es `channel`: un canal de valor por instancia que el censo llamaba
primitivo crudo. Cumple los tres requisitos de la clase, verificados uno
a uno.
Las otras tres agujas (`SYSTEM`, `STYLE_LAYER` y el `var(--` pelado) llevaban
el MISMO defecto con cero victimas hoy. Se corrigen igual: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa, y esta medido que no
cuesta nada — las cifras salen identicas con y sin ellas (cuatro variantes
construidas y medidas por el adversarial). La tolerancia esta COPIADA, no
inventada: `recipe-css-contract.test.ts:150`, el instrumento hermano de esta
misma casa sobre este mismo constructo, ya la tenia.
LEDGER: dos filas quedan STALE y se retiran con acta en la CABECERA (dentro
del mapa las borraria la proxima regeneracion). NO es deuda perdonada: ninguna
receta cambio, se corrigio una misclasificacion en su origen. Verificado por
cuatro vias independientes, la ultima byte a byte contra lo que el generador
escribiria, que no se borra ni una fila de deuda VIVA.
SUELO: `minAtHundred` 66 -> 67, y el acta dice la verdad sobre el mecanismo
porque el pase adversarial cazo que no la decia. NO es un re-base — en un
re-base se mueve la PREGUNTA y una multitud se mueve con ella sin haber
avanzado (14->45 al corregir el criterio; 45->65 cuando 252 knobs salieron del
denominador). Aqui no se mueve ni el criterio ni la doctrina, pero el
DENOMINADOR SI se mueve, por exactamente un knob: split-button cruza al 100 %
porque su knob no alcanzado SALE del ratio (`global` -> `channel`, excluida por
diseno), no porque ninguno se volviera publico. Lo que gana el alza de todos
modos es que su receta YA era correcta y el censo puntuaba ese trabajo bueno
como primitivo crudo. Misma lectura que la entrada de `tabs`, un piso mas
arriba. `reachPct` deliberadamente INTACTO: la otra victima mueve el ratio
72,7273 % -> 72,7682 %, que sigue siendo 73 — un suelo que subiera con eso
estaria citando ruido.
CIFRAS, predichas -> medidas: public 3072 -> 3073 · literal 325 -> 324 ·
channel 41 -> 42 · global 761 -> 760 · themeable 4224 -> 4223 · atHundred
66 -> 67 · ledger 1152 -> 1150 registered, 0 NEW, 0 STALE. Sobre 5187 knobs
escaneados cambian de clase EXACTAMENTE dos, y son los dos anunciados: cero
sobre-captura, medido por sonda independiente. Suite `theming-reach-floor`
verde; scope `src/uix/eidos` verde.
ANOTADO, no tocado (pre-existente, no de este lote): `scripts/theming-census.ts`
ya venia sucio de Prettier en HEAD — probado formateando la version intacta de
HEAD y obteniendo los mismos hunks desplazados +38, que es el delta de este
commit. Y `scripts/component-audit.ts:1137` arrastra el MISMO defecto de aguja
(cero victimas hoy). Las fichas generadas de split-button y palabras quedan
desfasadas hasta la proxima corrida de `--report`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
if ( refs . length === 0 ) return ! ANY_VAR . test ( v ) || pubNeedle . test ( v ) ;
uix(theming): el censo aprende que un privado DERIVADO sí alcanza — 50 % → 56 %
Seis puntos de alcance sin tocar una línea de componente: **la medición estaba
mal, no el código**.
EL DEFECTO. F0 encargaba «detección "privado que deriva de público" (leer
`--_c-x: var(--c-y)` en la misma receta y reclasificar)». Se implementó a
medias: el censo CALCULA `derives` y lo imprime en la columna «¿deriva de un
público?» de la §3 de cada ficha, pero **el contador nunca lo usó** — esos
knobs seguían puntuando como deuda. Medido: **240 privados en 59 componentes**
estaban en ese estado, es decir, haciendo exactamente lo que la doctrina
prescribe («privados sólo si derivan de públicos») y penalizados por ello.
Lo destapó tokenizar `avatar`: sus 17 privados YA leían públicos
(`--_avatar-size: var(--avatar-size-md)`), así que no había nada que tokenizar
— había que arreglar el instrumento.
LA CORRECCIÓN. Segunda pasada tras recoger privados y knobs: un knob que lee
`var(--_c-x)` pasa a `public` si ese privado alcanza un público. El cierre es
**TRANSITIVO** (`--_a: calc(var(--_b) * .3)` alcanza si `--_b` alcanza) con
conjunto de visitados para que un ciclo no lo cuelgue.
**Muta-prueba** (obligatoria, memoria `a-guard-that-inspects-nothing-passes`):
apuntar `--_avatar-radius` al primitivo crudo baja avatar de 75 % a 72 % y el
knob vuelve a `private`; revertirlo lo devuelve a 75 % y deja el árbol
idéntico.
EFECTO MEDIDO:
- Global **50 % → 56 %** · privados 625 → 341 · al 100 % 10 → 14 · <20 % 22 → 20.
- `avatar` 36 % → **75 %**, `picker-shell` 76 % → **95 %**, `menu-dial` 25 % →
38 %, y otras 51 fichas con cifras nuevas — **ninguna por cambio de código**.
- Los READMEs de `picker-shell` y `menu-dial` se actualizan con la cifra nueva
y con la razón, para que nadie crea que el componente cambió.
⚠ **Cualquier cifra anterior a este commit no es comparable con las de ahora.**
Queda escrito en el handoff.
Y un hueco de F0 que sale al hacerlo, registrado en §13: **el test del SUELO
que el plan pedía NO EXISTE** («alcance global ≥ el de hoy y literales ≤ 614 —
el número sube o el test falla»). Hoy nada impide que el alcance BAJE entre
sesiones: sólo se vería mirando la cifra a mano. Con el censo ya endurecido, es
el momento de escribirlo.
Gates: `component:audit` 162 PASS / 4 NEEDS-WORK (preexistentes) · `--names`
DESVIADAS 0 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · el
censo compila.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
return refs . every ( ( r ) = > derivesFromPublic ( r , seen ) ) ;
} ) ;
}
derivesCache . set ( name , ok ) ;
return ok ;
} ;
for ( const k of knobs ) {
if ( k . klass !== 'private' ) continue ;
const refs = [ . . . k . value . matchAll ( /var\(\s*(--_[a-z0-9-]+)/g ) ] . map ( ( m ) = > m [ 1 ] ) ;
if ( refs . length === 0 || ! refs . every ( ( r ) = > derivesFromPublic ( r ) ) ) continue ;
row . private -- ;
row . public ++ ;
k . klass = 'public' ;
}
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// ── Third pass: the palette BRIDGE and the value CHANNEL ──
// The two shapes of private the doctrine adjudicated as not-debt, decided by
// what the GENERATOR writes (see `PALETTE_BRIDGE`). `every`, not `some`: a
// knob mixing a bridge with anything else stays residue, because the
// anything else is precisely what still needs adjudicating. A FOREIGN
// private (another component's `--_x-…`) can be a bridge — the forward is
// the same mechanism wherever it is emitted — but never a channel: this
// component's CSS is not where it would be declared.
const own = '--_' + dir + '-' ;
refactor(theming)!: MeasuredIndicator escribe en el namespace del HOST - la capa es la pluma, no la dueña
Firma SS14 (2026-08-26, nombre poseido tras rechazar el nombre generico de
capa: habria repetido el defecto un piso abajo). La capa compartida
publicaba el rect medido como --indicator-{x,y,w,h} - forma PUBLICA sin
dueño, invisible para la ley del espacio cerrado (que audita --{c}-*) y
para todos los guards. Tercera aplicacion de la doctrina del canal
(navigation-menu fue la segunda, tabs la habia firmado por escrito citando
a navmenu como precedente).
- MeasuredIndicatorOpts gana `component: string`; los DOS hosts (tabs,
radio-group - barrido completo, no hay mas) pasan xxxMorfo.kebab, NUNCA
un literal: el nombre del canal queda atado a la declaracion que lo posee.
- tabs.css lee el poseido directo; el css COMPARTIDO (sliding-indicator)
lee el alias local que cada receta declara (--_indicator-x:
var(--_radio-group-indicator-x)) - el cableado del puente THM-2, contrato
en el artefacto. --indicator-{duration,ease} INTACTOS a proposito: son
ranuras de FORMA, no canal - el _ separa las dos especies en el mismo
fichero; su falta de dueño queda flagged como especie pendiente.
- El censo aprende el CANAL TRANSITIVO (molde de derivesFromPublic,
memoizado, a prueba de ciclos, con la mutacion que impide que un alias
sobre un privado declarado clasifique canal). Efecto colateral MEDIDO con
acta: tooltip.css:27 pasa de exception a channel - su comentario firmado
ya decia "per-instance VALUE CHANNEL"; faltaba el mecanismo, no el
criterio.
- Ledger: la clave tabs RETIRADA entera (2 entradas global) con acta que
hace la lectura honesta - no se tokenizo nada ni se perdono deuda: se
corrigio una mala clasificacion en su fuente. 1154 -> 1152, 0/0.
- Numeros: tabs 97% -> 100% (global 2->0, channel 0->2, ni una clave
acunada) - atHundred 65 -> 66 - reach global 73% y public 3072 intactos.
- Dinamica verbatim en los dos hosts (radio-group manifiesta en segmented):
el canal poseido LLEVA el rect del item activo (x 784=784, w 71=71 /
x 793=793, w 102=102), el indicador desliza, los nombres viejos computan
vacio, control negativo quieto y positivo probando la CADENA del alias.
Re-verificado DESPUES de aterrizar el codemod del peer. Sonda estandar de
tabs 2528 valores x 10 estados a 0 diffs - dicho en voz alta: esa sonda
es CIEGA al canal; su 0 prueba lo circundante.
- Guards: 154 tests verdes - centinela tabs 70/79 identico (ninguna
adjudicacion citaba las medidas) - audit 162 PASS - docs:check 0/0 -
src/ sigue a 0 errores.
ATRIBUCION DECLARADA (acordada por canal entre sesiones): este commit CARGA
ademas la retirada de 5+5 `syncAttrs: true` en tabs-provider y
radio-group-provider - trabajo de "P0 fase C2c, audit 2026-08-26 - retirada
de syncAttrs, sesion de la auditoria" (vicen-63), que aterrizo en disco
durante esta construccion y cuya restauracion habria sido churn sin valor.
Su commit de C2c anota el viaje desde el otro lado.
BREAKING: --indicator-{x,y,w,h} ya no existen; el canal es
--_{c}-indicator-* del host y el css compartido lee el alias por receta.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const isChannel = ( r : string ) = >
r . startsWith ( own ) && ! declBySource . has ( r ) && ! GENERATED_PRIVATES . has ( r ) ;
docs(theming): el dia queda cerrado - handoff 2026-08-26, causa corregida y suelo en 66
Cierre documental tras el adversarial de SS14 (firma sostenida; un claim de
ACTA cayo y se corrige aqui):
- La causa de la extension transitiva del censo era CONTRAFACTUAL en tres
sitios (census.ts, next-features, CONTINUE): el alias de SS14 no lleva
prefijo propio y el censo lo descarta antes - clasifica global con la
razon CORRECTA (medido construyendolo: 1 new, rojo). La extension se
justifica por tooltip (alias propio sobre canal propio), no por el alias.
Reescrita con el contrafactico medido.
- El suelo sube a minAtHundred 66 - SS14 llevo a tabs al 100% y el
adversarial cazo el suelo rezagado de su propia ley ("only moves up").
- SS15 NUEVO en next-features, redactado por el adversarial: el GEMELO un
piso arriba - floating.svelte.ts publica cinco medidas de instancia como
--floating-* (publico sin dueño, ~8 recetas lectoras); mas grande que SS14
porque el posicionador sirve a muchos hosts. Dimensionar antes de firmar.
- Handoff 2026-08-26: las cinco firmas del dia con sus adversariales, lo
primero de mañana por orden de valor (SS15 - la especie de forma sin dueño
- cableado motion - palabras/timeline), el estado de la rama compartida
(peer vicen-63 con su C2c/hook, que push falta) y las leyes nuevas del dia
(el nombre dice la verdad en las dos direcciones; grep ciego a llaves;
sonda ciega a canales; una causa sin contrafactico acaba corregida).
Suelo 5/5 con 66 - docs:check 0/0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// TRANSITIVE (firma §14, 2026-08-26) — and the honest record of WHY, corrected
// by the adversarial pass of the same day: the case that JUSTIFIES it is
// `tooltip` (an own-prefixed alias declared over an own-prefixed channel:
// `--_tooltip-content-width-override` → measured, it reclassified from
// `exception` to `channel`, which its signed comment had always claimed).
// The §14 alias of `radio-group` was the shape that PROMPTED the thought but
// never needed the rule: its alias (`--_indicator-*`) carries no own prefix,
// so the declaration filter above discards it before privates are even
// collected — a knob reading it classifies `global`, red, with the RIGHT
// reason. Same mould as `derivesFromPublic`
refactor(theming)!: MeasuredIndicator escribe en el namespace del HOST - la capa es la pluma, no la dueña
Firma SS14 (2026-08-26, nombre poseido tras rechazar el nombre generico de
capa: habria repetido el defecto un piso abajo). La capa compartida
publicaba el rect medido como --indicator-{x,y,w,h} - forma PUBLICA sin
dueño, invisible para la ley del espacio cerrado (que audita --{c}-*) y
para todos los guards. Tercera aplicacion de la doctrina del canal
(navigation-menu fue la segunda, tabs la habia firmado por escrito citando
a navmenu como precedente).
- MeasuredIndicatorOpts gana `component: string`; los DOS hosts (tabs,
radio-group - barrido completo, no hay mas) pasan xxxMorfo.kebab, NUNCA
un literal: el nombre del canal queda atado a la declaracion que lo posee.
- tabs.css lee el poseido directo; el css COMPARTIDO (sliding-indicator)
lee el alias local que cada receta declara (--_indicator-x:
var(--_radio-group-indicator-x)) - el cableado del puente THM-2, contrato
en el artefacto. --indicator-{duration,ease} INTACTOS a proposito: son
ranuras de FORMA, no canal - el _ separa las dos especies en el mismo
fichero; su falta de dueño queda flagged como especie pendiente.
- El censo aprende el CANAL TRANSITIVO (molde de derivesFromPublic,
memoizado, a prueba de ciclos, con la mutacion que impide que un alias
sobre un privado declarado clasifique canal). Efecto colateral MEDIDO con
acta: tooltip.css:27 pasa de exception a channel - su comentario firmado
ya decia "per-instance VALUE CHANNEL"; faltaba el mecanismo, no el
criterio.
- Ledger: la clave tabs RETIRADA entera (2 entradas global) con acta que
hace la lectura honesta - no se tokenizo nada ni se perdono deuda: se
corrigio una mala clasificacion en su fuente. 1154 -> 1152, 0/0.
- Numeros: tabs 97% -> 100% (global 2->0, channel 0->2, ni una clave
acunada) - atHundred 65 -> 66 - reach global 73% y public 3072 intactos.
- Dinamica verbatim en los dos hosts (radio-group manifiesta en segmented):
el canal poseido LLEVA el rect del item activo (x 784=784, w 71=71 /
x 793=793, w 102=102), el indicador desliza, los nombres viejos computan
vacio, control negativo quieto y positivo probando la CADENA del alias.
Re-verificado DESPUES de aterrizar el codemod del peer. Sonda estandar de
tabs 2528 valores x 10 estados a 0 diffs - dicho en voz alta: esa sonda
es CIEGA al canal; su 0 prueba lo circundante.
- Guards: 154 tests verdes - centinela tabs 70/79 identico (ninguna
adjudicacion citaba las medidas) - audit 162 PASS - docs:check 0/0 -
src/ sigue a 0 errores.
ATRIBUCION DECLARADA (acordada por canal entre sesiones): este commit CARGA
ademas la retirada de 5+5 `syncAttrs: true` en tabs-provider y
radio-group-provider - trabajo de "P0 fase C2c, audit 2026-08-26 - retirada
de syncAttrs, sesion de la auditoria" (vicen-63), que aterrizo en disco
durante esta construccion y cuya restauracion habria sido churn sin valor.
Su commit de C2c anota el viaje desde el otro lado.
BREAKING: --indicator-{x,y,w,h} ya no existen; el canal es
--_{c}-indicator-* del host y el css compartido lee el alias por receta.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// (cycle-safe via `seen`, memoized), and `every` throughout, so a knob mixing
// a channel with anything else stays residue.
//
// The rigour that keeps this from swallowing the class: an alias over a
// private that IS declared here resolves to THAT declaration's value, whose
// refs are either empty (a literal) or point somewhere that is not a channel
// — `refs.length > 0` is what makes the literal case return false. Only a
// chain that bottoms out in a name NOBODY declares reads as channel.
const channelCache = new Map < string , boolean > ( ) ;
const resolvesToChannel = ( name : string , seen = new Set < string > ( ) ) : boolean = > {
if ( isChannel ( name ) ) return true ;
if ( channelCache . has ( name ) ) return channelCache . get ( name ) ! ;
if ( seen . has ( name ) ) return false ;
seen . add ( name ) ;
const ok = ( privValues . get ( name ) ? ? [ ] ) . every ( ( v ) = > {
const refs = [ . . . v . matchAll ( /var\(\s*(--_[a-z0-9-]+)/g ) ] . map ( ( m ) = > m [ 1 ] ) ;
return refs . length > 0 && refs . every ( ( r ) = > resolvesToChannel ( r , seen ) ) ;
} ) ;
// `every` on an empty list is `true` — a name this component never declares
// is not an alias, it is a foreign private, and the guard above already
// refused it as a channel.
const declared = ( privValues . get ( name ) ? ? [ ] ) . length > 0 ;
channelCache . set ( name , ok && declared ) ;
return ok && declared ;
} ;
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
for ( const k of knobs ) {
if ( k . klass !== 'private' ) continue ;
const refs = [ . . . k . value . matchAll ( /var\(\s*(--_[a-z0-9-]+)/g ) ] . map ( ( m ) = > m [ 1 ] ) ;
if ( refs . length === 0 ) continue ;
const klass : KnobClass | null = refs . every ( ( r ) = > PALETTE_BRIDGE . has ( r ) )
? 'bridge'
refactor(theming)!: MeasuredIndicator escribe en el namespace del HOST - la capa es la pluma, no la dueña
Firma SS14 (2026-08-26, nombre poseido tras rechazar el nombre generico de
capa: habria repetido el defecto un piso abajo). La capa compartida
publicaba el rect medido como --indicator-{x,y,w,h} - forma PUBLICA sin
dueño, invisible para la ley del espacio cerrado (que audita --{c}-*) y
para todos los guards. Tercera aplicacion de la doctrina del canal
(navigation-menu fue la segunda, tabs la habia firmado por escrito citando
a navmenu como precedente).
- MeasuredIndicatorOpts gana `component: string`; los DOS hosts (tabs,
radio-group - barrido completo, no hay mas) pasan xxxMorfo.kebab, NUNCA
un literal: el nombre del canal queda atado a la declaracion que lo posee.
- tabs.css lee el poseido directo; el css COMPARTIDO (sliding-indicator)
lee el alias local que cada receta declara (--_indicator-x:
var(--_radio-group-indicator-x)) - el cableado del puente THM-2, contrato
en el artefacto. --indicator-{duration,ease} INTACTOS a proposito: son
ranuras de FORMA, no canal - el _ separa las dos especies en el mismo
fichero; su falta de dueño queda flagged como especie pendiente.
- El censo aprende el CANAL TRANSITIVO (molde de derivesFromPublic,
memoizado, a prueba de ciclos, con la mutacion que impide que un alias
sobre un privado declarado clasifique canal). Efecto colateral MEDIDO con
acta: tooltip.css:27 pasa de exception a channel - su comentario firmado
ya decia "per-instance VALUE CHANNEL"; faltaba el mecanismo, no el
criterio.
- Ledger: la clave tabs RETIRADA entera (2 entradas global) con acta que
hace la lectura honesta - no se tokenizo nada ni se perdono deuda: se
corrigio una mala clasificacion en su fuente. 1154 -> 1152, 0/0.
- Numeros: tabs 97% -> 100% (global 2->0, channel 0->2, ni una clave
acunada) - atHundred 65 -> 66 - reach global 73% y public 3072 intactos.
- Dinamica verbatim en los dos hosts (radio-group manifiesta en segmented):
el canal poseido LLEVA el rect del item activo (x 784=784, w 71=71 /
x 793=793, w 102=102), el indicador desliza, los nombres viejos computan
vacio, control negativo quieto y positivo probando la CADENA del alias.
Re-verificado DESPUES de aterrizar el codemod del peer. Sonda estandar de
tabs 2528 valores x 10 estados a 0 diffs - dicho en voz alta: esa sonda
es CIEGA al canal; su 0 prueba lo circundante.
- Guards: 154 tests verdes - centinela tabs 70/79 identico (ninguna
adjudicacion citaba las medidas) - audit 162 PASS - docs:check 0/0 -
src/ sigue a 0 errores.
ATRIBUCION DECLARADA (acordada por canal entre sesiones): este commit CARGA
ademas la retirada de 5+5 `syncAttrs: true` en tabs-provider y
radio-group-provider - trabajo de "P0 fase C2c, audit 2026-08-26 - retirada
de syncAttrs, sesion de la auditoria" (vicen-63), que aterrizo en disco
durante esta construccion y cuya restauracion habria sido churn sin valor.
Su commit de C2c anota el viaje desde el otro lado.
BREAKING: --indicator-{x,y,w,h} ya no existen; el canal es
--_{c}-indicator-* del host y el css compartido lee el alias por receta.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
: refs . every ( ( r ) = > resolvesToChannel ( r ) )
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
? 'channel'
: null ;
if ( klass === null ) continue ;
row . private -- ;
row [ klass ] ++ ;
k . klass = klass ;
}
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// ── Fourth pass: the WRITTEN valve for what no mechanical test can see ──
// LAST on purpose: the three passes above decide by MEASUREMENT, and a
// signature over a knob the machine already adjudicated would be an act with
// nothing left to sign. What survives to here is the residue — and a residue
// knob is `exception` when its own declaration carries `/* private: … * /`,
// or when EVERY private it reads does (see `ANNOTATED_PRIVATE`).
for ( const k of knobs ) {
if ( k . klass !== 'private' ) continue ;
const refs = [ . . . new Set ( [ . . . k . value . matchAll ( /var\(\s*(--_[a-z0-9-]+)/g ) ] . map ( ( m ) = > m [ 1 ] ) ) ] ;
if ( ! signedKnobs . has ( k ) && ! ( refs . length > 0 && refs . every ( ( r ) = > signedPrivates . has ( r ) ) ) )
continue ;
row . private -- ;
row . exception ++ ;
k . klass = 'exception' ;
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const themeable = row . public + row . private + row . global + row . literal ;
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
row . knobs = themeable + row . system + row . structural + row . bridge + row . channel ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
row . reach = themeable === 0 ? 1 : row.public / themeable ;
const declared = new Set ( privates . map ( ( p ) = > p . name ) ) ;
const privatesFromContract = [ . . . usedPrivates ]
. filter ( ( n ) = > n . startsWith ( '--_' + dir + '-' ) && ! declared . has ( n ) )
. sort ( ) ;
return { row , knobs , privates , privatesFromContract , files } ;
}
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
export function census ( only? : string ) : CensusRow [ ] {
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
return censusAudit ( only ) . rows ;
}
// ─── The debt ledger — R-5.1's per-key ratchet ───────────────────────────────
/ * *
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* The THREE unreached classes the ratchet registers key by key .
*
* ` private ` joined ` global ` and ` literal ` on 2026 - 08 - 25 ( F2 of the
* 318 - privates adjudication ) , and with it the hole the ledger ' s header used to
* name is closed : a knob reading a private that derives from nothing — not a
* public , not the palette bridge , not a value channel , and carrying no
* ` /* private: … * / ` signature — is now debt like any other . It has the same
* three exits ( tokenize it , sign it , or register it ) and the same STALE
* detector on the way out .
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* /
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
export type DebtClass = Extract < KnobClass , ' global ' | ' literal ' | ' private ' > ;
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const isDebt = ( k : Knob ) : boolean = >
k . klass === 'global' || k . klass === 'literal' || k . klass === 'private' ;
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/ * *
* The canonical identity of one debt knob : ` {class} · {file} · {selector} ·
* { property } ` .
*
* LINE - INDEPENDENT on purpose . A ledger keyed by ` file:line ` would report a
* regression every time a rule moved down three lines , and the noise would
* teach everyone to regenerate the baseline — which is exactly how a ratchet
* dies . The cost of the choice is that a SELECTOR rename reads as one STALE
* plus one new key ; that is the ratchet working , not a defect ( the debt moved
* and gets re - signed ) .
* /
export function debtKey ( k : Knob ) : string {
return ` ${ k . klass } · ${ k . file } · ${ k . selector } · ${ k . prop } ` ;
}
export interface DebtFinding {
component : string ;
key : string ;
}
export interface DebtAudit {
/** Debt in the CSS that no ledger entry covers — a regression. */
newDebt : DebtFinding [ ] ;
/** Ledger entries with no debt behind them — tokenized, annotated or dead. */
stale : DebtFinding [ ] ;
/** Ledger entries in scope (the size of the frozen debt). */
registered : number ;
/** Per component, for the audit rows. */
byComponent : Map < string , { newDebt : string [ ] ; stale : string [ ] ; registered : number } > ;
}
export interface CensusAudit {
rows : CensusRow [ ] ;
/** component → its debt keys today, sorted (the multiset the ledger freezes). */
today : Map < string , string [ ] > ;
debt : DebtAudit ;
}
/** Occurrence count — the comparison is a MULTISET, so a duplicate is caught. */
function tally ( xs : string [ ] ) : Map < string , number > {
const m = new Map < string , number > ( ) ;
for ( const x of xs ) m . set ( x , ( m . get ( x ) ? ? 0 ) + 1 ) ;
return m ;
}
/ * *
* ONE pass over the tree that answers both questions the axis asks : the
* classification ( ` rows ` ) and the state of the ledger ( ` debt ` ) . Two passes
* would double the cost for every consumer that needs both — ` component-audit `
* needs exactly that for its R - 5.1 and R - 5.2 rows .
* /
export function censusAudit ( only? : string ) : CensusAudit {
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const rows : CensusRow [ ] = [ ] ;
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const today = new Map < string , string [ ] > ( ) ;
// Sorted so the generated baseline is byte-identical between runs, whatever
// order the filesystem hands the directories back in.
for ( const dir of [ . . . readdirSync ( ROOT ) ] . sort ( ) ) {
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( only && dir !== only ) continue ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const scan = scanComponent ( dir ) ;
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
if ( ! scan ) continue ;
rows . push ( scan . row ) ;
const keys = scan . knobs . filter ( isDebt ) . map ( debtKey ) . sort ( ) ;
if ( keys . length > 0 ) today . set ( dir , keys ) ;
}
const newDebt : DebtFinding [ ] = [ ] ;
const stale : DebtFinding [ ] = [ ] ;
const byComponent = new Map < string , { newDebt : string [ ] ; stale : string [ ] ; registered : number } > ( ) ;
let registered = 0 ;
const components = [ . . . new Set ( [ . . . today . keys ( ) , . . . Object . keys ( CENSUS_DEBT ) ] ) ] . sort ( ) ;
for ( const component of components ) {
if ( only && component !== only ) continue ;
const ledger = CENSUS_DEBT [ component ] ? ? [ ] ;
const now = today . get ( component ) ? ? [ ] ;
const a = tally ( now ) ;
const b = tally ( ledger ) ;
const entry = { newDebt : [ ] as string [ ] , stale : [ ] as string [ ] , registered : ledger.length } ;
for ( const [ key , n ] of a )
for ( let i = 0 ; i < n - ( b . get ( key ) ? ? 0 ) ; i ++ ) entry . newDebt . push ( key ) ;
for ( const [ key , n ] of b ) for ( let i = 0 ; i < n - ( a . get ( key ) ? ? 0 ) ; i ++ ) entry . stale . push ( key ) ;
entry . newDebt . sort ( ) ;
entry . stale . sort ( ) ;
registered += ledger . length ;
if ( entry . newDebt . length > 0 || entry . stale . length > 0 || entry . registered > 0 )
byComponent . set ( component , entry ) ;
for ( const key of entry . newDebt ) newDebt . push ( { component , key } ) ;
for ( const key of entry . stale ) stale . push ( { component , key } ) ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
}
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
return { rows , today , debt : { newDebt , stale , registered , byComponent } } ;
}
/ * *
* Prettier ' s own quoting rule ( fewest escapes ) : single quotes unless the
* string carries one and no double quote . Selectors DO carry single quotes
* ( ` [data-size='sm'] ` ) , so getting this wrong means the generated baseline
* fails ` npm run lint ` — and a baseline that cannot be committed clean is a
* baseline nobody regenerates correctly .
* /
function quote ( s : string ) : string {
if ( s . includes ( "'" ) && ! s . includes ( '"' ) ) return '"' + s . replace ( /\\/g , '\\\\' ) + '"' ;
return "'" + s . replace ( /\\/g , '\\\\' ) . replace ( /'/g , "\\'" ) + "'" ;
}
/** The ledger's own source, rewritten below its doctrinal header. */
async function writeDebtBaseline() {
const path = resolve ( 'scripts/theming-census-debt.ts' ) ;
const prev = readFileSync ( path , 'utf8' ) . replace ( /\r\n/g , '\n' ) ;
const marker = '\nexport const CENSUS_DEBT: Record<string, string[]> = ' ;
const at = prev . indexOf ( marker ) ;
if ( at < 0 ) throw new Error ( 'theming-census-debt.ts: no `export const CENSUS_DEBT` to replace' ) ;
const { today , debt } = censusAudit ( ) ;
const body = [ . . . today . entries ( ) ]
. map (
( [ component , keys ] ) = >
` \ t ${ / ^ [ a - z ] [ a - z0 - 9 ] * $ / . test ( component ) ? component : quote ( component ) } : [ \ n ` +
keys . map ( ( k ) = > ` \ t \ t ${ quote ( k ) } ` ) . join ( ',\n' ) +
'\n\t]'
)
. join ( ',\n' ) ;
const source = prev . slice ( 0 , at ) + marker + '{\n' + body + '\n};\n' ;
// Formatted by prettier itself, not by hand: the file must land clean under
// `prettier --check .`, and prettier is deterministic, so two generations of
// the same census produce the same bytes.
const prettier = ( await import ( 'prettier' ) ) as unknown as {
resolveConfig ( p : string ) : Promise < Record < string , unknown > | null > ;
format ( src : string , opts : Record < string , unknown > ) : Promise < string > ;
} ;
const config = ( await prettier . resolveConfig ( path ) ) ? ? { } ;
writeFileSync ( path , await prettier . format ( source , { . . . config , filepath : path } ) , 'utf8' ) ;
const total = [ . . . today . values ( ) ] . reduce ( ( a , k ) = > a + k . length , 0 ) ;
console . log (
` theming-census --debt --write — ${ total } debt key(s) across ${ today . size } component(s) `
) ;
console . log (
` delta vs the ledger being replaced: + ${ debt . newDebt . length } new · - ${ debt . stale . length } retired `
) ;
if ( debt . newDebt . length > 0 || debt . stale . length > 0 )
console . log ( ' READ THAT DELTA: regenerating wholesale erases the ratchet (see the header).' ) ;
}
function reportDebt ( only? : string , asJson = false ) {
const { debt } = censusAudit ( only ) ;
// A check mode that cannot fail is half a guard: red findings set the exit
// code so `--debt` can gate a script or a hook, not only a human reader.
if ( debt . newDebt . length > 0 || debt . stale . length > 0 ) process . exitCode = 1 ;
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
if ( asJson ) {
process . stdout . write (
JSON . stringify ( { newDebt : debt.newDebt , stale : debt.stale } , null , '\t' ) + '\n'
) ;
return ;
}
console . log (
` theming-census --debt — ${ debt . registered } key(s) registered · ${ debt . newDebt . length } new · ${ debt . stale . length } stale `
) ;
if ( debt . newDebt . length > 0 ) {
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
console . log ( '\n— NEW DEBT (a literal, a raw global or a bare private outside the ledger) —' ) ;
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
for ( const f of debt . newDebt ) console . log ( ` ${ f . component } ${ f . key } ` ) ;
}
if ( debt . stale . length > 0 ) {
console . log ( '\n— STALE (registered as debt, no longer debt: delete the line) —' ) ;
for ( const f of debt . stale ) console . log ( ` ${ f . component } ${ f . key } ` ) ;
}
if ( debt . newDebt . length === 0 && debt . stale . length === 0 )
console . log ( ' the ledger matches the CSS exactly.' ) ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
}
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// ─── `--residue`: the privates that are NEITHER bridge NOR channel ───────────
/ * *
* What the two mechanical signatures deliberately did NOT decide .
*
* The bridge and the channel are adjudicated by measurement ; everything else
* that reads a private without deriving from a public is a per - KEY question —
* a switch whose branches are the identity of a variant ( ` toolbar ` ) , a
* conmutador the TSC feeds ( ` avatar ` ) , a private that reads a raw global in
* one branch ( ` proof-of-human ` ) . Each row carries the knob AND where every
* private it reads is declared , with the class of each declaration , because
* that is the whole material the adjudication needs .
*
feat(theming): los 150 privados del residuo quedan adjudicados - 84 firmados, 66 contados
Segunda pieza de la firma de los 318 (F2). El residuo que F1 dejo se
adjudica POR CLAVE, cada acta con su medida: 150 = 84 ANOTADOS + 66 al
LEDGER + 0 sin nombre.
- Valvula nueva /* private: <razon> */ -> clase exception (vocabulario
cerrado). Corre en una CUARTA pasada, la ultima: donde una prueba mecanica
ya decidio, una firma no tiene nada que firmar. La nota va en el bloque de
comentario ENCIMA de la declaracion (al final de linea, prettier partiria
el var() - un commit de anotacion no reescribe el CSS que viene a firmar);
el paseo hacia arriba solo salta lineas que eran solo-comentario.
- private es DebtClass: clave `private - {fichero} - {selector} -
{propiedad}`, newDebt y STALE simetricos. Un privado no-derivado nuevo ya
no puede entrar sin nombre. Ledger 1088 -> 1154 (+66, 0 claves perdidas,
verificado contra el blob de HEAD, no regenerado en bloque).
- ANOTADOS 84: 59 con ficha SS5 firmada que lo dice (card, avatar, badge,
surface, switch, timeline, textarea, spinner, skeleton, metrics,
float-panel, image, toolbar, button, drawer, listbox) + 25 medidos
(proof-of-human 17: el puente UN NIVEL bajo el knob, base verbatim del
forward, medido dos veces; form 4: conmutador-identidad; 4 sueltos).
- LEDGER 66: casi todo UN patron - el conmutador de tono A MANO
([data-color=risk] { --_c-accent: var(--color-risk-solid) }) sin adoptar
el forward THM-2 (time-range-picker 19, chronos 14, time-picker 12,
date-range-picker 9...) mas las tallas prestadas de field. Se retiran el
dia que esa familia adopte la escalera de paleta.
- Contradiccion con ficha RESUELTA por medida: metrics SS5 decia que el
conmutador del icono derivaba entero de publicos - cierto para track/text,
falso para solid/contrast (leen roles crudos): 3 anotados, 2 al ledger.
timeline SS5 (pre-B') se respeta y queda señalado para reabrir.
Mutaciones (backup+restore en finally, arbol comprobado): privado nuevo ->
newDebt lo nombra y suelo ROJO; anotado -> verde; registrado -> verde;
ledger'd que gana su nota -> STALE hasta borrar la linea. --debt final:
1154 registered - 0 new - 0 stale, dos corridas. Suelo 5/5, audit 162 PASS
sin flips, tsc 0 propios, diff de los 22 css = 100% comentario (verificado
por strip programatico contra el blob de HEAD), prettier 0 regresiones.
Reach 71% -> 73% (3074/4228). El suelo (69/45) y la prosa "318" los sube F3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* It was a REPORT and not a gate until 2026 - 08 - 25 ; since F2 of that signature
* the residue is gated like every other unreached class ( ` DebtClass ` above ) ,
* so this listing is now the WORKING VIEW of the adjudication — it shows what
* is left to decide , with the material each decision needs , while the ledger
* and the ` private: ` valve hold the answer already given .
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* /
export interface ResidueRow {
component : string ;
file : string ;
line : number ;
selector : string ;
prop : string ;
value : string ;
/** One entry per private read: `--_x → file:line class | file:line class`. */
sources : string [ ] ;
}
export function residue ( only? : string ) : ResidueRow [ ] {
const out : ResidueRow [ ] = [ ] ;
for ( const dir of [ . . . readdirSync ( ROOT ) ] . sort ( ) ) {
if ( only && dir !== only ) continue ;
const scan = scanComponent ( dir ) ;
if ( ! scan ) continue ;
for ( const k of scan . knobs ) {
if ( k . klass !== 'private' ) continue ;
const refs = [ . . . new Set ( [ . . . k . value . matchAll ( /var\(\s*(--_[a-z0-9-]+)/g ) ] . map ( ( m ) = > m [ 1 ] ) ) ] ;
out . push ( {
component : dir ,
file : k.file ,
line : k.line ,
selector : k.selector ,
prop : k.prop ,
value : k.value ,
sources : refs.map ( ( r ) = > {
const decls = scan . privates . filter ( ( p ) = > p . name === r ) ;
if ( decls . length > 0 )
return r + ' → ' + decls . map ( ( d ) = > ` ${ d . file } : ${ d . line } ${ d . source } ` ) . join ( ' | ' ) ;
return r + ' → ' + ( GENERATED_PRIVATES . has ( r ) ? 'generated/base.css' : 'undeclared' ) ;
} )
} ) ;
}
}
return out ;
}
function reportResidue ( only? : string , out? : string , asJson = false ) {
const rows = residue ( only ) ;
if ( asJson ) {
process . stdout . write ( JSON . stringify ( rows , null , '\t' ) + '\n' ) ;
return ;
}
const per = new Map < string , number > ( ) ;
for ( const r of rows ) per . set ( r . component , ( per . get ( r . component ) ? ? 0 ) + 1 ) ;
const tsv =
[ 'component' , 'file:line' , 'selector' , 'property' , 'value' , 'sources' ] . join ( '\t' ) +
'\n' +
rows
. map ( ( r ) = >
[
r . component ,
` ${ r . file } : ${ r . line } ` ,
r . selector ,
r . prop ,
r . value ,
r . sources . join ( ' ;; ' )
] . join ( '\t' )
)
. join ( '\n' ) +
'\n' ;
console . log (
` theming-census --residue — ${ rows . length } private knob(s) in ${ per . size } component(s): ` +
'neither the palette bridge nor a value channel, so each one is a decision.'
) ;
console . log (
' worst: ' +
[ . . . per . entries ( ) ]
. sort ( ( a , b ) = > b [ 1 ] - a [ 1 ] )
. slice ( 0 , 12 )
. map ( ( [ c , n ] ) = > ` ${ c } : ${ n } ` )
. join ( ' ' )
) ;
if ( out ) {
writeFileSync ( resolve ( out ) , tsv , 'utf8' ) ;
console . log ( ` wrote ${ out } ` ) ;
return ;
}
process . stdout . write ( '\n' + tsv ) ;
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// ─── The proposal — derived from doctrine, never invented ────────────────────
/ * *
* property → token slot . recipe - contract § 1 ( dimensional names : logical axes ,
* ` {part}-height-{size} ` , ` padding-inline[-{size}] ` , ` gap ` , ` radius ` ,
* ` font-size-{size} ` , ` icon-size-{size} ` ) + theming § 6.7 ( colour slots :
* ` bg, fg, border, on-bg, on-fg, on-border, hover-bg, on-hover-bg ` ) .
* ` null ` = the system owns it ; a recipe must not mint a token for it .
* /
const PROP_SLOT : Record < string , string | null > = {
background : 'bg' ,
'background-color' : 'bg' ,
'background-image' : 'bg-image' ,
color : 'fg' ,
'border-color' : 'border' ,
'border-width' : 'border-width' ,
'border-radius' : 'radius' ,
'box-shadow' : 'shadow' ,
outline : null ,
'outline-color' : null ,
'outline-width' : null ,
'padding-inline' : 'padding-inline' ,
'padding-block' : 'padding-block' ,
'padding-inline-start' : 'padding-inline' ,
'padding-inline-end' : 'padding-inline' ,
'padding-block-start' : 'padding-block' ,
'padding-block-end' : 'padding-block' ,
gap : 'gap' ,
'row-gap' : 'row-gap' ,
'column-gap' : 'column-gap' ,
'font-size' : 'font-size' ,
'font-weight' : 'font-weight' ,
'font-family' : 'font-family' ,
'line-height' : 'line-height' ,
'letter-spacing' : 'letter-spacing' ,
'min-block-size' : 'height' ,
'block-size' : 'height' ,
'min-inline-size' : 'width' ,
'inline-size' : 'width' ,
opacity : 'opacity' ,
fill : 'fill' ,
stroke : 'stroke' ,
filter : 'filter' ,
'backdrop-filter' : 'backdrop-filter' ,
'accent-color' : 'accent' ,
'caret-color' : 'caret' ,
'text-decoration-color' : 'underline'
} ;
/** Physical axes are forbidden as token keys (R-4.4) — they need a decision. */
const PHYSICAL_PROPS = new Set ( [
'padding' ,
'padding-top' ,
'padding-bottom' ,
'padding-left' ,
'padding-right' ,
'border' ,
'border-top' ,
'border-bottom' ,
'border-left' ,
'border-right' ,
'border-block' ,
'border-inline' ,
'border-block-start' ,
'border-block-end' ,
'border-inline-start' ,
'border-inline-end' ,
'height' ,
'min-height' ,
'width' ,
'min-width'
] ) ;
const SIZE_KEYS = [ 'xxs' , 'xs' , 'sm' , 'md' , 'lg' , 'xl' , 'xxl' ] ;
/** Shared layers own an axis; a consumer must NOT mint `--{c}-{axis}` for it. */
const SHARED_LAYERS : Record < string , string [ ] > = {
'list-surface' : [
'color-field' ,
'combobox' ,
'command' ,
'context-menu' ,
'dropdown-menu' ,
'fab' ,
'listbox' ,
'menu-dial' ,
'menubar' ,
'select'
] ,
'menu-indicator' : [
'context-menu' ,
'dropdown-menu' ,
'grid-list' ,
'listbox' ,
'menubar' ,
'navigation-menu'
] ,
feat(calendar-surface): la capa que ya existía, con nombre, casa y su agujero tapado
Firma 1 del acta, diseño presentado y firmado. **La medición desmontó el
encargo**: el handoff la vendía como una capa NUEVA de ~220 knobs, y la capa ya
existía de hecho, sin nombre. `--calendar-*` se emite en `:root` (76 claves) y
sus consumidores no acuñan NADA — medido: `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.
**`lib/calendar-surface.css`** (hook `data-calendar-surface`):
- cuatro coordenadas por talla — `padding`, `control-size`, `day-size`,
`font-size` — xs..lg, porque la familia NO tiene xl, y con la celda DOS pasos
por debajo del bundle de control. Esa desviación estaba escondida en cuatro
bloques `[data-size]` idénticos, uno por receta, cada uno puenteando a un
privado con otro nombre; ahora se lee en un sitio.
- la FORMA del anillo de evento y de la marca de festivo.
**Capa HÍBRIDA, y es lo que la distingue de sus hermanas**: `list-surface` y
`viewport-placement` componen primitivos del sistema, así que declaran sus
públicos en el fichero y no tienen entrada de receta. Ésta no puede: su
vocabulario son 76 claves SEMÁNTICAS que un tema alcanza una a una por config,
así que la entrada `calendar` de `recipes/base.ts` pasa a ser la de la FAMILIA
y la capa posee sólo lo que una entrada de receta no sabe expresar.
**El defecto que la justificaba, medido**: `--calendar-event-shadow` y
`--calendar-day-holiday-shadow` se emitían con ámbito `[data-calendar]`
(audit B.2 los host-scopeó por buenas razones) mientras `range-calendar`,
`month-grid` y `year-grid` los leían desde hosts que nunca llevan ese atributo:
variable VACÍA, `box-shadow` inválido en computed, **el anillo sema de
`commit-select` / `commit-set` no pintaba jamás en tres componentes**. Un token
prestado cuyo ÁMBITO no te cubre no es un préstamo, es un agujero silencioso, y
ningún guard lo veía. Ahora la forma vive en la capa y el acento entra por
`--_calendar-surface-accent`, que cada superficie alimenta con su propio
forward de paleta THM-2.
**Siete wrappers estampan, no cuatro** — y esto casi se me cuela: `DatePicker`
y `DateRangePicker` 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 del root del
picker. Con sólo los cuatro standalone sellando, ambos quedaban con
`--calendar-padding` VACÍA y el panel a padding 0 (medido). 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.
computed 0 diffs en range-calendar (19.285 valores) · month-grid (3.451) ·
year-grid (3.451) · date-picker (464) · date-range-picker (406).
`calendar` da 12, y son del INSTRUMENTO: dos corridas del MISMO
código dan 24 en los mismos nodos y las mismas dos propiedades.
Los «missing node» son el propio sello entrando en la clave.
**Una incidencia nueva, medida y NO arreglada aquí** (§13): el font-size de los
selectores month/year es una moneda al aire —
`[data-calendar-month-select][data-button]` (0,2,0) empata con
`[data-popover-trigger]:not([data-archetype='field-trigger'])` (0,2,0), la
MISMA regla de popover que dejó muerto el cromo de `gradient-picker`, y gana la
hoja que cargue después: 16px o 14px según la recarga. Arreglarlo fija el píxel
en un lado ⇒ decisión.
De paso, `calendar-select.css` deja de puentear un privado que sólo
`[data-calendar]` declaraba: en range / date-range corría SIEMPRE por el
fallback, clavado a md fuera cual fuera la talla.
**El censo deja de penalizar hacer lo correcto**: `LAYER_VOCABULARY` en
`theming-census.ts`, mismo precedente que D-TH.2-b con `--style-*`. Global
43 % → **45 %**, `calendar` 75 % → **84 %**. `list-surface` NO se registra: sus
consumidores puentean por privados, otra forma, y mueve diez componentes de
golpe. Y aparece el techo de debajo, anotado: a los tres consumidores sólo les
quedan los forwards de paleta THM-2 —que el censo cuenta como `private` en TODO
el catálogo— así que siguen leyendo 0 %.
`recipe-css-contract` aprende que una CAPA también declara públicos (antes sólo
miraba la receta, y una capa que comparte prefijo con un componente la hacía
fallar). Sin debilitarla: un nombre que no declara nadie sigue en rojo.
eidos-lint 0 invalid (calendar 29/8 · range-calendar 41/12 · los grids 23/5) ·
audit --only calendar PASS · vitest eidos 434/435 (el rojo conocido) ·
rtl 0/181 · docs 0/813 · check 0 errores en tocados · prettier: revertido el
reformateo en masa que se coló en cuatro README, el test y el censo (churn
ajeno, no mío)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'calendar-surface' : [ 'calendar' , 'date-picker' , 'month-grid' , 'range-calendar' , 'year-grid' ] ,
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'sliding-indicator' : [ 'radio-group' ] ,
'viewport-placement' : [ 'affix' , 'fab' , 'menu-dial' ] ,
'spin-field' : [ 'css-field' , 'knob' , 'number-field' ] ,
'field-segment-state' : [
'color-field' ,
'color-picker' ,
'date-field' ,
'date-range-picker' ,
'time-field'
] ,
'picker-shell' : [
'chronos' ,
'color-picker' ,
'date-picker' ,
'date-range-picker' ,
'gradient-builder' ,
'gradient-picker' ,
'month-grid' ,
'natural-time-picker' ,
'palabras' ,
'time-picker' ,
'time-range-picker' ,
'year-grid'
]
} ;
export interface Proposal {
/** The token key as it goes into `lib/recipes/base.ts` (no `--{c}-` prefix). */
key : string | null ;
/** The value — verbatim from today's CSS, so the default cannot move. */
value : string ;
/** TSC scope (canon/tsc.md): root · host · color:X · variant:X · size:X. */
scope : string ;
/** Why there is no mechanical name (⚠ decisión) — or '' when there is. */
warning : string ;
knob : Knob ;
}
/** part · size · variant · color · state, read off the selector. */
function readSelector ( selector : string , component : string ) {
// `:not(:disabled)` asserts the ABSENCE of a state — reading it as the state
// produced `hover-disabled-trigger-bg` for a plain hover. Strip the negations
// before looking for anything.
const sel = selector . replace ( /:not\([^)]*\)/g , '' ) ;
const partMatch = sel . match ( new RegExp ( '\\[data-' + component + '-([a-z0-9-]+)[\\]=]' ) ) ;
const part = partMatch ? partMatch [ 1 ] : '' ;
const size = sel . match ( /\[data-size='([a-z]+)'\]/ ) ? . [ 1 ] ? ? '' ;
const variant = sel . match ( /\[data-variant='([a-z-]+)'\]/ ) ? . [ 1 ] ? ? '' ;
const color = sel . match ( /\[data-color='([a-z-]+)'\]/ ) ? . [ 1 ] ? ? '' ;
const state : string [ ] = [ ] ;
if ( /:hover/ . test ( sel ) ) state . push ( 'hover' ) ;
if ( /:active|\[data-pressed\]/ . test ( sel ) ) state . push ( 'press' ) ;
if ( /\[data-state='open'\]|\[data-open\]/ . test ( sel ) ) state . push ( 'open' ) ;
if ( /\[data-state='checked'\]|\[data-checked\]/ . test ( sel ) ) state . push ( 'on' ) ;
if ( /\[data-state='selected'\]|\[data-selected\]/ . test ( sel ) ) state . push ( 'selected' ) ;
if ( /\[data-disabled\]|:disabled/ . test ( sel ) ) state . push ( 'disabled' ) ;
if ( /\[data-invalid\]/ . test ( sel ) ) state . push ( 'invalid' ) ;
if ( /:focus-visible|\[data-focused\]/ . test ( sel ) ) state . push ( 'focus' ) ;
return { part , size , variant , color , state } ;
}
/** Layout literals are geometry, not a theme knob — flagged, never named. */
const LAYOUT_LITERAL = /^(100%|100vw|100dvh|100vh|1|auto)$/ ;
function propose ( knob : Knob , scan : Scan ) : Proposal [ ] {
const component = scan . row . component ;
const { part , size , variant , color , state } = readSelector ( knob . selector , component ) ;
const slot = PROP_SLOT [ knob . prop ] ;
const base = ( s : string ) = > [ part , s ] . filter ( Boolean ) . join ( '-' ) ;
const blocked = ( warning : string ) : Proposal [ ] = > [
{ key : null , value : knob.value , scope : '—' , warning , knob }
] ;
if ( slot === null )
return blocked (
'foco: lo posee el sistema (`--focus-ring-*`, theming §32) — no acuñar token propio'
) ;
if ( PHYSICAL_PROPS . has ( knob . prop ) )
return blocked (
'⚠ decisión: `' +
knob . prop +
'` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)'
) ;
if ( ! slot ) return blocked ( '⚠ decisión: la propiedad no tiene slot canónico en el vocabulario' ) ;
if ( /--state-|--focus-ring/ . test ( knob . value ) )
return blocked (
'capa de estado / anillo de foco: sistema transversal — no acuñar (recipe-contract §2)'
) ;
if ( LAYOUT_LITERAL . test ( knob . value ) )
return blocked (
'⚠ decisión: `' +
knob . value +
'` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar'
) ;
// The colour slot vocabulary puts the modifier FIRST (theming §6.7:
// `hover-bg`, `on-bg`); the dimensional one puts the size LAST
// (recipe-contract §1: `padding-inline-{size}`).
const mod = state . filter ( ( s ) = > s !== 'focus' ) . join ( '-' ) ;
const name = ( sizeKey : string ) = > [ variant , mod , base ( slot ) , sizeKey ] . filter ( Boolean ) . join ( '-' ) ;
fix(scripts): la estela de M0 — habia OCHO agujas ciegas al corte de Prettier, no una
M0 (`830cd7671`) curo cinco agujas del censo de theming y su cuerpo dejo tres
cabos anotados. Al tirar de ellos aparece que el defecto era mucho mas ancho:
un barrido de `scripts/` entero, con TRES ortografias de la aguja (literal
`var\(--`, cadena `'var\\(--'` y grupo `var\((--`), encuentra SEIS mas. Se
corrigen cinco y se declara la sexta.
POR QUE IMPORTA MAS QUE EN EL CENSO. La aguja del censo produce una MALA
CLASIFICACION, y una mala clasificacion al menos se cuenta en algun sitio.
Estas producen sobre todo FALSOS NEGATIVOS: el guard deja de mirar y PASA en
silencio. Cinco de las seis son de ese sentido.
LAS AGUJAS (todas con CERO victimas hoy, medido antes de tocar; se corrigen por
la misma razon que M0 escribio para las suyas sin victima: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa):
- `theming-census.ts:1267` `scopeFor` — LA DE CLASE VIVA, y esta EN EL FICHERO
QUE M0 DECLARO CURADO, comiendo de la MISMA tuberia (`knob.value`,
normalizado a espacios simples en :579). Escribiria alcance `root` donde la
verdad es `host`. Medido el catalogo: 5187 knobs + 1497 privados, 12 valores
llevan hoy `var( --` y ninguno contiene `palette-`.
- `theming-census.ts:2244` `valenced` y `:2246` el rol neutro — el segundo mata
el clasificador `state-layer` entero: sin match, el knob nunca se encola.
Su entrada es `base.ts`, que NADIE normaliza, y el lector de PRODUCCION de
esos mismos valores ya es tolerante (`render-css.ts` VAR_REF_REGEX).
- `theming-sentinel.ts:867` `kindOfTerm` y `:884` los operandos de
`calc/min/max/clamp` — el segundo es el caso que el propio comentario de
arriba documenta (22 longitudes leidas como numeros). Probado por
EQUIVALENCIA sobre las 4566 entradas de base.ts: 0 diferencias de kind ni de
sentinel entre HEAD y la version tolerante.
- `component-audit.ts:957 / :1137 / :1329` (R-2.6, R-4.3, R-4.6) — la 4.3 era
la peor del lote: opera sobre un valor SIN normalizar, tomado directo del
fuente, y su error se INVIERTE (falso positivo: acusa de «capa de estado
artesanal» a una declaracion que lee la del sistema).
- `__statelayer-analysis.ts:28` — NO TOCADA y declarada: su lector es POR
LINEA, asi que un corte trunca el valor ANTES de que la aguja corra. Anadir
tolerancia daria falsa sensacion de cobertura mientras la limitacion real
sigue en pie. (De paso, defecto preexistente NO tocado en :32: un
`new RegExp` con comillas DOBLES hace que su `\s` sea solo `s` y esa regex no
case nunca.)
`\s*` va a AMBOS lados donde la aguja cierra parentesis, porque el corte produce
`var( --x )`; solo a la izquierda donde no lo cierra. La tolerancia esta COPIADA
del hermano de la casa (`recipe-css-contract.test.ts:150`), no inventada.
DOS CORRECCIONES DOCUMENTALES que el pase adversarial caza:
- `component-audit.ts` ≈:1154 justificaba no usar `/g` con un mecanismo FALSO
(«lastIndex viaja entre llamadas»). Medido: un literal inline se re-crea en
cada evaluacion (`true,true,true`) y sobrevive a la bandera POR ACCIDENTE de
donde esta escrito; el arrastre es del OBJETO regex izado a const de modulo
(`true,FALSE,true`). La decision de no usar `/g` sigue siendo buena y ahora
se sostiene en su razon verdadera. El acta del censo queda como esta: sus
agujas SI estan izadas y alli el arrastre es real.
- `theming-census-debt.ts:58-59` se contradecia con su propia linea :96 tras
la reescritura de la caveat: `87 literal` -> `86` y `583 of the 1154 keys` ->
`582 of the 1150`. Verificado contando el ledger (palabras 272+86+1=359 ·
chronos 198+11+14=223 · 582 de 1150) y con `--debt` («1150 registered, 0 new,
0 stale»). La aritmetica cierra con las actas del propio fichero:
1154 −2 (`8ac23047f`) = 1152 −2 (`830cd7671`) = 1150.
CAVEAT DEL LEDGER reescrita: las claves `--palabras-*` ya NO estan «EN
CUESTION» — la clasificacion esta ADJUDICADA (canal de valor del scheme del
documento) y lo que queda pendiente de firma es la EJECUCION, no el veredicto.
Se anade el dato que faltaba y que cambia como se lee el numero: de los 117
nombres consumidos, el esquema por defecto emite 74 y **43 no los emite nunca**,
de modo que para esos el fallback del CSS ES el valor de diseno. (La particion
que yo habia dado, 75/42, era la resta equivocada: suponia que lo emitido es
subconjunto de lo consumido, y `--palabras-scheme-mark-highlight-bg` se emite y
ninguna receta lo lee.)
FICHAS NO REGENERADAS a proposito: `--only` se parsea pero `writeReport()` lo
ignora, asi que regenerar reescribiria las 171 fichas + README — 172 ficheros
mezclando todos los carriles. Las de split-button y palabras quedan desfasadas
hasta que alguien corra `--report` sobre un arbol quieto.
VERIFICADO: censo antes/despues byte a byte IDENTICO · `component:audit`
PASS 161 / NEEDS-WORK 5 / BROKEN 0, con `tmp/component-audit.md` byte a byte
salvo la linea `Date:` autogenerada · `theming-reach-floor` 5/5 verde · ningun
fichero bajo `web/` tocado (regla del autor: ese arbol se reconstruye).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// `\s*` after the paren: same needle fix as `classify` (ACTA above), and the
// same input — `knob.value` / a private's value, both normalized to single
// spaces, so a prettier-broken declaration arrives as `var( --`. Blind here
// the proposal would scope a palette value `root` instead of `host`.
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const scopeFor = ( value : string , sizeKey : string ) = >
color
? ` color: ${ color } `
: sizeKey && ! size
? ` size: ${ sizeKey } `
fix(scripts): la estela de M0 — habia OCHO agujas ciegas al corte de Prettier, no una
M0 (`830cd7671`) curo cinco agujas del censo de theming y su cuerpo dejo tres
cabos anotados. Al tirar de ellos aparece que el defecto era mucho mas ancho:
un barrido de `scripts/` entero, con TRES ortografias de la aguja (literal
`var\(--`, cadena `'var\\(--'` y grupo `var\((--`), encuentra SEIS mas. Se
corrigen cinco y se declara la sexta.
POR QUE IMPORTA MAS QUE EN EL CENSO. La aguja del censo produce una MALA
CLASIFICACION, y una mala clasificacion al menos se cuenta en algun sitio.
Estas producen sobre todo FALSOS NEGATIVOS: el guard deja de mirar y PASA en
silencio. Cinco de las seis son de ese sentido.
LAS AGUJAS (todas con CERO victimas hoy, medido antes de tocar; se corrigen por
la misma razon que M0 escribio para las suyas sin victima: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa):
- `theming-census.ts:1267` `scopeFor` — LA DE CLASE VIVA, y esta EN EL FICHERO
QUE M0 DECLARO CURADO, comiendo de la MISMA tuberia (`knob.value`,
normalizado a espacios simples en :579). Escribiria alcance `root` donde la
verdad es `host`. Medido el catalogo: 5187 knobs + 1497 privados, 12 valores
llevan hoy `var( --` y ninguno contiene `palette-`.
- `theming-census.ts:2244` `valenced` y `:2246` el rol neutro — el segundo mata
el clasificador `state-layer` entero: sin match, el knob nunca se encola.
Su entrada es `base.ts`, que NADIE normaliza, y el lector de PRODUCCION de
esos mismos valores ya es tolerante (`render-css.ts` VAR_REF_REGEX).
- `theming-sentinel.ts:867` `kindOfTerm` y `:884` los operandos de
`calc/min/max/clamp` — el segundo es el caso que el propio comentario de
arriba documenta (22 longitudes leidas como numeros). Probado por
EQUIVALENCIA sobre las 4566 entradas de base.ts: 0 diferencias de kind ni de
sentinel entre HEAD y la version tolerante.
- `component-audit.ts:957 / :1137 / :1329` (R-2.6, R-4.3, R-4.6) — la 4.3 era
la peor del lote: opera sobre un valor SIN normalizar, tomado directo del
fuente, y su error se INVIERTE (falso positivo: acusa de «capa de estado
artesanal» a una declaracion que lee la del sistema).
- `__statelayer-analysis.ts:28` — NO TOCADA y declarada: su lector es POR
LINEA, asi que un corte trunca el valor ANTES de que la aguja corra. Anadir
tolerancia daria falsa sensacion de cobertura mientras la limitacion real
sigue en pie. (De paso, defecto preexistente NO tocado en :32: un
`new RegExp` con comillas DOBLES hace que su `\s` sea solo `s` y esa regex no
case nunca.)
`\s*` va a AMBOS lados donde la aguja cierra parentesis, porque el corte produce
`var( --x )`; solo a la izquierda donde no lo cierra. La tolerancia esta COPIADA
del hermano de la casa (`recipe-css-contract.test.ts:150`), no inventada.
DOS CORRECCIONES DOCUMENTALES que el pase adversarial caza:
- `component-audit.ts` ≈:1154 justificaba no usar `/g` con un mecanismo FALSO
(«lastIndex viaja entre llamadas»). Medido: un literal inline se re-crea en
cada evaluacion (`true,true,true`) y sobrevive a la bandera POR ACCIDENTE de
donde esta escrito; el arrastre es del OBJETO regex izado a const de modulo
(`true,FALSE,true`). La decision de no usar `/g` sigue siendo buena y ahora
se sostiene en su razon verdadera. El acta del censo queda como esta: sus
agujas SI estan izadas y alli el arrastre es real.
- `theming-census-debt.ts:58-59` se contradecia con su propia linea :96 tras
la reescritura de la caveat: `87 literal` -> `86` y `583 of the 1154 keys` ->
`582 of the 1150`. Verificado contando el ledger (palabras 272+86+1=359 ·
chronos 198+11+14=223 · 582 de 1150) y con `--debt` («1150 registered, 0 new,
0 stale»). La aritmetica cierra con las actas del propio fichero:
1154 −2 (`8ac23047f`) = 1152 −2 (`830cd7671`) = 1150.
CAVEAT DEL LEDGER reescrita: las claves `--palabras-*` ya NO estan «EN
CUESTION» — la clasificacion esta ADJUDICADA (canal de valor del scheme del
documento) y lo que queda pendiente de firma es la EJECUCION, no el veredicto.
Se anade el dato que faltaba y que cambia como se lee el numero: de los 117
nombres consumidos, el esquema por defecto emite 74 y **43 no los emite nunca**,
de modo que para esos el fallback del CSS ES el valor de diseno. (La particion
que yo habia dado, 75/42, era la resta equivocada: suponia que lo emitido es
subconjunto de lo consumido, y `--palabras-scheme-mark-highlight-bg` se emite y
ninguna receta lo lee.)
FICHAS NO REGENERADAS a proposito: `--only` se parsea pero `writeReport()` lo
ignora, asi que regenerar reescribiria las 171 fichas + README — 172 ficheros
mezclando todos los carriles. Las de split-button y palabras quedan desfasadas
hasta que alguien corra `--report` sobre un arbol quieto.
VERIFICADO: censo antes/despues byte a byte IDENTICO · `component:audit`
PASS 161 / NEEDS-WORK 5 / BROKEN 0, con `tmp/component-audit.md` byte a byte
salvo la linea `Date:` autogenerada · `theming-reach-floor` 5/5 verde · ningun
fichero bajo `web/` tocado (regla del autor: ese arbol se reconstruye).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
: /var\(\s*--[a-z-]*palette-/ . test ( value )
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
? 'host'
: 'root' ;
const note = ( value : string , sizeKey : string ) = > {
if ( color )
return 'declaración por color: `declarations[]` con scope `color:' + color + '` (tsc.md)' ;
if ( sizeKey && ! SIZE_KEYS . includes ( sizeKey ) ) return '⚠ decisión: talla fuera del canon' ;
if ( /calc\(|min\(|max\(|clamp\(/ . test ( value ) )
return '⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable' ;
return '' ;
} ;
// A knob reading a PRIVATE resolves through it: the public token takes the
// private's own value, and if the private is redeclared per size, the token
// is minted per size (recipe-contract §1: `{part}-{eje}-{k}`).
if ( knob . klass === 'private' ) {
const ref = knob . value . match ( new RegExp ( 'var\\(\\s*(--_' + component + '-[a-z0-9-]+)' ) ) ? . [ 1 ] ;
const decls = ref ? scan . privates . filter ( ( p ) = > p . name === ref ) : [ ] ;
// The private already reads a public: the knob IS reachable and there is
// nothing to mint. The census counts it as `private` because a regex cannot
// follow the indirection (PLAN-theming §1.3) — that is a measurement limit,
// not debt.
if ( decls . length > 0 && decls . every ( ( d ) = > d . source === 'public' ) )
return blocked (
'ya alcanzable: el privado `' +
ref +
'` deriva de un público (' +
list ( [ . . . new Set ( decls . map ( ( d ) = > d . value ) ) ] . slice ( 0 , 4 ) ) +
') — sin acción; el censo lo cuenta como no alcanzable por el límite del regex'
) ;
if ( decls . length > 0 ) {
const resolved = decls . map ( ( d ) = > ( {
size : readSelector ( d . selector , component ) . size ,
value : d.value
} ) ) ;
const multi = resolved . filter ( ( r ) = > r . size ) . length > 1 ;
// The size-less declaration in a per-size set IS the default step: the
// catalogue names every one of them (`font-size-{size}`), so leaving it
// bare would mint a name the vocabulary does not have.
if ( multi ) {
const present = new Set ( resolved . map ( ( r ) = > r . size ) . filter ( Boolean ) ) ;
const missing = SIZE_KEYS . filter ( ( k ) = > ! present . has ( k ) && present . size >= 2 ) ;
const dflt = missing . includes ( 'md' ) ? 'md' : '' ;
for ( const r of resolved ) if ( ! r . size && dflt ) r . size = dflt ;
}
return resolved . map ( ( r ) = > ( {
key : name ( multi ? r.size : size ) ,
value : r.value ,
scope : scopeFor ( r . value , multi ? r . size : '' ) ,
warning :
( note ( r . value , multi ? r.size : size )
? note ( r . value , multi ? r.size : size ) + ' · '
: '' ) +
'el privado `' +
ref +
'` debe pasar a leer este público (o desaparecer)' ,
knob
} ) ) ;
}
return blocked (
'⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo'
) ;
}
return [
{
key : name ( size ) ,
value : knob.value ,
scope : scopeFor ( knob . value , '' ) ,
warning : note ( knob . value , size ) ,
knob
}
] ;
}
// ─── Report ──────────────────────────────────────────────────────────────────
const MARK_START = '<!-- veredicto:start -->' ;
const MARK_END = '<!-- veredicto:end -->' ;
const VERDICT_SEED =
MARK_START + '\n\n_(pendiente — lo escribe el autor; se conserva al regenerar)_\n\n' + MARK_END ;
const pct = ( n : number , d : number ) = > ( d === 0 ? '—' : ` ${ Math . round ( ( 100 * n ) / d ) } % ` ) ;
const cell = ( s : string ) = > '`' + s . replace ( /\|/g , '\\|' ) + '`' ;
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
/** `strct` and not `0%`: the row has no denominator, and that IS the answer. */
const reachOf = ( r : CensusRow ) = >
STRUCTURAL_COMPONENTS . has ( r . component )
? 'strct'
: pct ( r . public , r . public + r . private + r . global + r . literal ) ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const list = ( xs : string [ ] ) = > xs . map ( ( k ) = > '`' + k + '`' ) . join ( ', ' ) ;
/ * *
* A token BORROWED from another recipe — verified against that recipe ' s own
* keys in ` base.ts ` , not by prefix . ` --icon-size-sm ` is the global icon scale ,
* not a token of the ` icon ` component , and a prefix test called it a loan .
* /
const OWNER_KEYS = new Map < string , Set < string > > ( ) ;
function borrowedFrom ( value : string , self : string ) : string [ ] {
const owners = new Set < string > ( ) ;
for ( const m of value . matchAll ( /var\(\s*--(_?)([a-z0-9-]+)/g ) ) {
const priv = m [ 1 ] === '_' ;
const name = m [ 2 ] ;
for ( const c of COMPONENT_DIRS ) {
if ( c === self || ! name . startsWith ( c + '-' ) ) continue ;
if ( ! OWNER_KEYS . has ( c ) ) OWNER_KEYS . set ( c , new Set ( contractKeyNames ( c ) ) ) ;
const key = ( priv ? '_' : '' ) + name . slice ( c . length + 1 ) ;
if ( OWNER_KEYS . get ( c ) ! . has ( key ) ) owners . add ( c ) ;
}
}
return [ . . . owners ] . sort ( ) ;
}
function knobTable ( rows : Knob [ ] , self : string ) : string {
if ( rows . length === 0 ) return '_Ninguno._\n' ;
const out = [
'| # | fichero:línea | selector | propiedad | valor |' ,
'| ---: | --- | --- | --- | --- |'
] ;
rows . forEach ( ( k , i ) = > {
const borrow = borrowedFrom ( k . value , self ) ;
const val = cell ( k . value ) + ( borrow . length ? ` ⤴ prestado de ${ list ( borrow ) } ` : '' ) ;
out . push (
` | ${ i + 1 } | ${ cell ( k . file + ':' + k . line ) } | ${ cell ( k . selector ) } | ${ cell ( k . prop ) } | ${ val } | `
) ;
} ) ;
return out . join ( '\n' ) + '\n' ;
}
function proposalSection ( scan : Scan ) : string {
const c = scan . row . component ;
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// `structural` out with `public` and `system`: proposing a token for a knob
// whose §5 verdict says there is no contract to write would be inventing debt.
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// `bridge` and `channel` out for the same reason, one floor down: a public
// over the palette forward or over a value channel is a token a theme could
// PIN, and pinning it kills the per-instance `color=` / the behaviour. The
// sheets of `card`, `tags-input` and `tabs` had to refuse exactly that
// proposal by hand — the classifier must not keep making it.
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const targets = scan . knobs . filter (
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
( k ) = >
k . klass !== 'public' &&
k . klass !== 'system' &&
k . klass !== 'structural' &&
k . klass !== 'bridge' &&
k . klass !== 'channel'
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
) ;
if ( targets . length === 0 )
return STRUCTURAL_COMPONENTS . has ( c )
? '_Ninguna: el componente es **estructural** (§2-bis) — no hay contrato que escribir._\n'
: '_Nada que proponer: no hay knobs fuera de alcance._\n' ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const proposals = targets . flatMap ( ( k ) = > propose ( k , scan ) ) ;
const minted = new Map < string , { value : Set < string > ; scope : string ; uses : number } > ( ) ;
const blocked : Proposal [ ] = [ ] ;
for ( const p of proposals ) {
if ( ! p . key ) {
blocked . push ( p ) ;
continue ;
}
const e = minted . get ( p . key ) ? ? { value : new Set < string > ( ) , scope : p.scope , uses : 0 } ;
e . value . add ( p . value ) ;
e . uses ++ ;
minted . set ( p . key , e ) ;
}
const out : string [ ] = [ ] ;
out . push (
` ### 4.1 Tokens a declarar en \` lib/recipes/base.ts \` ( ${ minted . size } ) ` ,
'' ,
'Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién' ,
'puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla' ,
'al final) y theming §6.7 (slots de color, modificador delante). Un token con' ,
'DOS valores distintos es una colisión de nombre: son dos knobs, o el nombre' ,
'no distingue lo que debería — se marca `⚠`.' ,
'' ,
'| token (`--' + c + '-…`) | scope TSC | valor propuesto | usos |' ,
'| --- | --- | --- | ---: |'
) ;
const existing = new Set ( contractKeyNames ( c ) ) ;
for ( const [ key , e ] of [ . . . minted . entries ( ) ] . sort ( ( a , b ) = > b [ 1 ] . uses - a [ 1 ] . uses ) ) {
const values = [ . . . e . value ] ;
const shown =
values . length === 1 ? cell ( values [ 0 ] ) : '⚠ ' + values . map ( ( v ) = > cell ( v ) ) . join ( ' / ' ) ;
const name = cell ( key ) + ( existing . has ( key ) ? ' _(ya existe)_' : '' ) ;
out . push ( ` | ${ name } | ${ cell ( e . scope ) } | ${ shown } | ${ e . uses } | ` ) ;
}
out . push ( '' ) ;
if ( blocked . length > 0 ) {
const byWarn = new Map < string , Knob [ ] > ( ) ;
for ( const p of blocked ) byWarn . set ( p . warning , [ . . . ( byWarn . get ( p . warning ) ? ? [ ] ) , p . knob ] ) ;
out . push ( ` ### 4.2 Sin nombre mecánico ( ${ blocked . length } ) ` , '' ) ;
for ( const [ warn , ks ] of byWarn ) {
out . push ( ` - ** ${ warn } ** — ${ ks . length } : ` + list ( [ . . . new Set ( ks . map ( ( k ) = > k . prop ) ) ] ) + '.' ) ;
}
out . push ( '' ) ;
}
const withWarn = proposals . filter ( ( p ) = > p . key && p . warning ) ;
if ( withWarn . length > 0 ) {
const byWarn = new Map < string , Set < string > > ( ) ;
for ( const p of withWarn )
byWarn . set ( p . warning , ( byWarn . get ( p . warning ) ? ? new Set ( ) ) . add ( '--' + c + '-' + p . key ) ) ;
out . push ( ` ### 4.3 Avisos sobre los tokens propuestos ( ${ byWarn . size } ) ` , '' ) ;
for ( const [ warn , keys ] of byWarn ) out . push ( ` - ** ${ warn } ** — ${ list ( [ . . . keys ] ) } ` ) ;
out . push ( '' ) ;
}
return out . join ( '\n' ) ;
}
function doctrineNotes ( scan : Scan ) : string {
const c = scan . row . component ;
const notes : string [ ] = [ ] ;
for ( const [ layer , consumers ] of Object . entries ( SHARED_LAYERS ) ) {
if ( consumers . includes ( c ) )
notes . push (
` - **Consume la capa compartida \` ${ layer } \` .** Un eje que la capa posee se consume como \` var(--_x, var(--x)) \` ; el consumidor **no acuña** \` -- ${ c } -{eje} \` para él — sería un vocabulario paralelo (README de \` eidos/components \` , «Capas compartidas» regla 2). `
) ;
}
const borrow = new Set < string > ( ) ;
for ( const k of scan . knobs ) for ( const o of borrowedFrom ( k . value , c ) ) borrow . add ( o ) ;
if ( borrow . size > 0 )
notes . push (
` - **Consume tokens públicos de ${ list ( [ . . . borrow ] . sort ( ) ) } .** Un token prestado importa la semántica de su dueño: la corrección no es duplicarlo con prefijo propio, sino la decisión de familia que la auditoría de fase 1 dejó registrada ( \` theming-audit.md \` §B, familia calendar). `
) ;
if ( scan . row . knobs === 0 )
notes . push (
'- **Sin knobs de apariencia**: el visual vive en un componente compuesto o en una capa compartida. «Declarado y nunca pintado» no es deuda por sí solo (architecture/eidos.md, columna `unused`).'
) ;
if ( scan . row . hasSize )
notes . push (
` - **Tiene eje \` size \` **: los tokens dimensionales van por talla ( \` {part}-{eje}-{k} \` ) apuntando al bundle \` --size-{k}-* \` , nunca al primitivo crudo (theming §5; el guard \` recipe-css-contract \` prohíbe el primitivo). `
) ;
return notes . length ? notes . join ( '\n' ) + '\n' : '' ;
}
function privateTable ( scan : Scan ) : string {
if ( scan . privates . length === 0 ) return '_La receta no declara privados propios en su CSS._\n' ;
const byName = new Map < string , PrivateDecl [ ] > ( ) ;
for ( const p of scan . privates ) byName . set ( p . name , [ . . . ( byName . get ( p . name ) ? ? [ ] ) , p ] ) ;
const rows = [ . . . byName . entries ( ) ] . map ( ( [ name , decls ] ) = > {
const sources = [ . . . new Set ( decls . map ( ( d ) = > d . source ) ) ] . sort ( ) ;
const derives = sources . length === 1 && sources [ 0 ] === 'public' ;
const values = [ . . . new Set ( decls . map ( ( d ) = > d . value ) ) ] ;
const shown = list ( values . slice ( 0 , 6 ) ) + ( values . length > 6 ? ` …(+ ${ values . length - 6 } ) ` : '' ) ;
return ` | ${ cell ( name ) } | ${ decls . length } | ${ shown } | ${ sources . join ( ', ' ) } | ${ derives ? '**sí**' : 'no' } | ` ;
} ) ;
return (
[
'| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |' ,
'| --- | ---: | --- | --- | :-: |' ,
. . . rows
] . join ( '\n' ) + '\n'
) ;
}
/ * *
* A component directory with NO ` .css ` at all : nothing to measure , and that is
* an answer , not a gap . Where its visual actually lives is MEASURED — the
* components it composes , the shared layer it consumes , the foreign recipe that
* styles its attrs — never guessed .
* /
export interface EmptyScan {
component : string ;
files : string [ ] ;
/** eidos components imported by its wrappers. */
composes : string [ ] ;
/** shared layers its wrappers import. */
layers : string [ ] ;
/** other components' CSS that selects `[data-{c}…]`. */
styledIn : string [ ] ;
contractKeys : string [ ] ;
}
const SHARED_LAYER_FILES = [
'list-surface' ,
'menu-indicator' ,
'sliding-indicator' ,
'viewport-placement' ,
'field-segment-state'
] ;
export function scanEmpty ( dir : string ) : EmptyScan | null {
const d = join ( ROOT , dir ) ;
if ( ! statSync ( d ) . isDirectory ( ) ) return null ;
const files = readdirSync ( d ) ;
if ( files . some ( ( f ) = > f . endsWith ( '.css' ) ) ) return null ;
const src = files
. filter ( ( f ) = > f . endsWith ( '.svelte' ) || f . endsWith ( '.ts' ) )
. map ( ( f ) = > readFileSync ( join ( d , f ) , 'utf8' ) )
. join ( '\n' ) ;
const composes = COMPONENT_DIRS . filter (
( c ) = > c !== dir && new RegExp ( 'components/' + c + "['/]" ) . test ( src )
) ;
const layers = SHARED_LAYER_FILES . filter ( ( l ) = > src . includes ( l ) ) ;
const styledIn : string [ ] = [ ] ;
for ( const other of COMPONENT_DIRS ) {
if ( other === dir ) continue ;
const od = join ( ROOT , other ) ;
for ( const f of readdirSync ( od ) . filter ( ( x ) = > x . endsWith ( '.css' ) ) ) {
if ( new RegExp ( '\\[data-' + dir + '[\\]-]' ) . test ( readFileSync ( join ( od , f ) , 'utf8' ) ) )
styledIn . push ( ` ${ other } / ${ f } ` ) ;
}
}
return { component : dir , files , composes , layers , styledIn , contractKeys : contractKeyNames ( dir ) } ;
}
function emptySheet ( scan : EmptyScan , today : string , verdict : string ) : string {
const c = scan . component ;
const why : string [ ] = [ ] ;
if ( scan . composes . length > 0 )
why . push (
` - **Compone** ${ list ( scan . composes ) } : su apariencia es la de ${ scan . composes . length === 1 ? 'ese componente, y se tema en SU ficha' : 'esos componentes, y se tema en SUS fichas' } . Un wrapper no vuelve a pintar lo que compone (component-guide §4). `
) ;
if ( scan . layers . length > 0 )
why . push (
` - **Consume la capa compartida** ${ list ( scan . layers ) } : la geometría la posee la capa, y el consumidor no acuña \` -- ${ c } -{eje} \` (README de \` eidos/components \` , «Capas compartidas»). `
) ;
if ( scan . styledIn . length > 0 )
why . push (
` - **Sus attrs se pintan desde otra receta**: ${ list ( scan . styledIn ) } . Esos knobs YA están contados en la ficha del anfitrión — contarlos aquí sería contarlos dos veces. `
) ;
if ( why . length === 0 )
why . push (
'- **No pinta**: el wrapper aporta estructura, formato o comportamiento, sin superficie propia que un tema pueda mover.'
) ;
const contract =
scan . contractKeys . length > 0
? ` \ n> ⚠ **Tiene ${ scan . contractKeys . length } clave(s) en \` lib/recipes/base.ts \` sin receta que las consuma** ( ${ list ( scan . contractKeys ) } ). Es la deuda INVERSA de este eje — un token público sin consumidor real; \` recipe-css-contract.test.ts \` falla si un alias público queda huérfano. Disposición: consumirlo o podarlo. \ n `
: '' ;
return ` # ${ c } — sin receta CSS
> Generado por \ ` node --import tsx/esm scripts/theming-census.ts --report \` .
> Vista de conjunto : [ README ] ( . / README . md ) · m é todo :
> [ \ ` PLAN-theming.md \` ](../../process/PLAN-theming.md) §1.
- * * Medido * * : $ { today } · * * Alcance * * : * * — * * ( no hay knobs que medir )
- * * Ficheros del componente * * : $ { list ( scan . files ) } — * * ning ú n \ ` .css \` **
- * * Contrato en \ ` lib/recipes/base.ts \` **: ${ scan . contractKeys . length === 0 ? 'sin entrada' : ` ${ scan . contractKeys . length } clave(s) ` }
$ { contract }
# # 1 . D ó nde vive su visual
$ { why . join ( '\n' ) }
« Declarado y nunca pintado » * * no es deuda por s í solo * * : el morfo declara la
superficie de COMPORTAMIENTO del componente , no s ó lo la pintable
( architecture / eidos . md , columna \ ` unused \` ). Un componente sin receta sólo es
deuda si declar ó un eje visual que nadie consume — el aviso de arriba lo dice
cuando ocurre .
# # 2 . Propuesta de correcci ó n
* * Ninguna . * * Sin receta no hay knob que tokenizar , y acu ñ ar tokens para un
componente que no pinta crear í a un vocabulario hu é rfano . Si alg ú n d í a pinta ,
entra por la puerta normal : receta + tokens en \ ` base.ts \` + esta ficha con
cifras .
# # 3 . Veredicto
$ { verdict }
` ;
}
function sheet ( scan : Scan , today : string , verdict : string ) : string {
const { row } = scan ;
const c = row . component ;
const themeable = row . public + row . private + row . global + row . literal ;
const keys = contractKeyNames ( c ) ;
const pub = keys . filter ( ( k ) = > ! k . startsWith ( '_' ) ) ;
const priv = keys . filter ( ( k ) = > k . startsWith ( '_' ) ) ;
const by = ( k : KnobClass ) = > scan . knobs . filter ( ( x ) = > x . klass === k ) ;
const contractLine =
keys . length === 0
? '**sin entrada en `base.ts`**'
: ` ${ pub . length } pública(s) ${ pub . length ? ` — ${ list ( pub ) } ` : '' } ${ priv . length ? ` · ${ priv . length } privada(s) forward — ${ list ( priv ) } ` : '' } ` ;
const notes = doctrineNotes ( scan ) ;
return ` # ${ c } — alcance de tema: análisis y propuesta
> Generado por \ ` node --import tsx/esm scripts/theming-census.ts --report \` .
> Lo * * medido * * y la * * propuesta * * se regeneran ; el * * Veredicto * * ( § 5 ) se conserva .
> Vista de conjunto : [ README ] ( . / README . md ) · m é todo y protocolo :
> [ \ ` PLAN-theming.md \` ](../../process/PLAN-theming.md) §1, §2, §7.
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
- * * Medido * * : $ { today } · * * Alcance * * : * * $ { reachOf ( row ) } * * — $ {
row . structural > 0
? 'ESTRUCTURAL: sin denominador que medir, y eso es la respuesta'
: ` ${ row . public } de ${ themeable } knobs por token público `
}
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
- * * Knobs de apariencia * * : $ { row . knobs } — p ú blico $ { row . public } · privado $ { row . private } · global $ { row . global } · literal $ { row . literal } · sistema $ { row . system } · excepci ó n $ { row . exception } · estructural $ { row . structural } · puente $ { row . bridge } · canal $ { row . channel } _ ( los cinco ú ltimos , fuera del ratio ) _
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- * * Contrato hoy * * ( \ ` lib/recipes/base.ts \` ): ${ contractLine }
- * * Eje \ ` size \` **: ${ row . hasSize ? 'sí' : 'no' } · **ficheros**: ${ list ( scan . files ) }
# # 1 . Knobs fuera de alcance
# # # 1.1 Directo a primitivo global ( $ { by ( 'global' ) . length } )
$ { knobTable ( by ( 'global' ) , c ) }
# # # 1.2 A trav é s de un privado ( $ { by ( 'private' ) . length } )
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
$ {
row . bridge + row . channel > 0
? '\nSólo el **residuo**: el puente de paleta (§2-ter) y el canal de valor\n(§2-quater) salen aparte, porque no son deuda ni tienen nombre que acuñar.\n'
: ''
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
$ { knobTable ( by ( 'private' ) , c ) }
# # # 1.3 Literales ( $ { by ( 'literal' ) . length } )
$ { knobTable ( by ( 'literal' ) , c ) }
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception`
Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda
resultan ser desviaciones que el canon ya había firmado.
`recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */`
EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre
(`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el
único instrumento que no: su `strip()` blanquea los comentarios ANTES de
clasificar, así que las **81 anotaciones del catálogo** contaban como literales
sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la
del que no sabía leerlo.
Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como
`system`, y por la misma razón: no es que un tema no lo alcance, es que el canon
ya aceptó por escrito que ahí no va un token.
**54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que
su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human`
16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**),
`result` 1 (86 % → **90 %**), y siete más con una cada uno.
Detalles que costaron medición:
- **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final. Se comprueban
todas las líneas que abarca la declaración.
- **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los
saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea
y basta con guardar el crudo al lado.
- Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a
`literal` (21 → 20) y el conteo vuelve al restaurarla.
Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que
las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna
parte; ahora la ficha del componente las enseña, que es donde se decide si una
sigue siendo cierta.
Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de
reparto); el README de la auditoría gana la fila de la clase y la nota de que
`system` y `exception` quedan fuera del denominador.
Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check
0 · `check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
# # # 1.4 Excepciones firmadas ( $ { by ( 'exception' ) . length } ) — fuera del ratio
Literales que llevan su anotaci ó n \ ` /* literal: <razón> */ \` en la propia
declaraci ó n : la v á lvula de recipe - contract § 3 , la misma que honra
\ ` component-audit \` . **Una desviación firmada no es deuda** — se listan para que la
raz ó n se lea , no para acu ñ arlas .
$ { knobTable ( by ( 'exception' ) , c ) } # # 2 . Sistema transversal ( $ { row . system } ) — informativo , fuera del ratio
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Un tema los alcanza * * a nivel de sistema * * , por dise ñ o ( recipe - contract § 2 ) .
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
$ { knobTable ( by ( 'system' ) , c ) } $ {
row . structural > 0
? `
# # 2 - bis . Estructural ( $ { row . structural } ) — fuera del ratio
Su 0 % es * * naturaleza , no deuda * * : $ { STRUCTURAL_COMPONENTS . get ( c ) } .
La lista firmada vive en \ ` STRUCTURAL_COMPONENTS \` ( \` scripts/theming-census.ts \` ) y
el porqu é del eje en § 13 de \ ` next-features.md \` . Los knobs se listan para que se
lean , * * no * * para acu ñ arlos .
$ { knobTable ( by ( 'structural' ) , c ) } `
: ''
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
} $ {
row . bridge > 0
? `
# # 2 - ter . Puente de paleta THM - 2 ( $ { row . bridge } ) — fuera del ratio
La receta lee \ ` var(--_ ${ c } -palette-{slot}) \` , que **lo escribe el forward** de la
cascada de paleta bajo \ ` [data- ${ c } ]:where([data-color], [data-color-custom]) \`
( \ ` renderRecipePaletteForward \` , firma B′ ). Es el MECANISMO de la paleta por
instancia , y se alcanza * * dos veces * * : por la capa compartida \ ` --palette-* \` y
por los tonos p ú blicos del propio componente . * * No se acu ñ a * * : un p ú blico
encima dejar í a que un tema lo fijara y matara en silencio el \ ` color= \` de cada
instancia ( veredictos § 5 de \ ` card \` , \` tags-input \` , \` avatar \` ).
$ { knobTable ( by ( 'bridge' ) , c ) } `
: ''
} $ {
row . channel > 0
? `
# # 2 - quater . Canal de valor ( $ { row . channel } ) — fuera del ratio
La receta lee un privado que * * nadie declara * * — ni su CSS ni el generador : lo
escribe soma o el envoltorio * * por instancia * * ( un % , un rect medido , la talla
que pide una prop ) . Un tema no debe alcanzarlo : fijarlo rompe el
comportamiento , y por eso \ ` tabs \` rechazó por escrito esa misma propuesta.
$ { knobTable ( by ( 'channel' ) , c ) } `
: ''
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
# # 3 . Privados de la receta — ¿ de d ó nde sale su valor ?
$ { privateTable ( scan ) } $ {
scan . privatesFromContract . length > 0
? ` \ nConsumidos y **no declarados en el CSS** (vienen de \` base.ts \` o de un estilo inline del wrapper): ${ list ( scan . privatesFromContract ) } . \ n `
: ''
}
# # 4 . Propuesta de correcci ó n
$ { notes ? notes + '\n' : '' } $ { proposalSection ( scan ) }
# # # 4.4 Lo que hay que comprobar a mano ( PLAN - theming § 1.3 · § 7.4 )
- [ ] * * Privado que no deriva de un p ú blico * * — § 3 lo marca ; el privado debe leer el p ú blico o desaparecer .
- [ ] * * Velo o acento en el nodo equivocado * * ( \ ` archetype: 'item' \` en un envoltorio, un \` background \` en shorthand que mata la capa de estado) — se mide desde el píxel hacia arriba.
- [ ] * * Doble animaci ó n * * al mover un sello a una superficie con animaci ó n propia — registro de \ ` animationstart \` / \` animationend \` .
- [ ] * * Diff de computed = 0 * * en reposo · hover · abierto · disabled · foco , por talla , antes y despu é s .
- [ ] * * Centinela por token nuevo * * : valor imposible en el root → el nodo lo sigue . Si no , el token miente .
# # 5 . Veredicto
$ { verdict }
` ;
}
function rootReadme ( scans : Scan [ ] , empties : EmptyScan [ ] , today : string , verdict : string ) : string {
const rows = scans . map ( ( s ) = > s . row ) ;
const sum = ( k : keyof CensusRow ) = > rows . reduce ( ( a , r ) = > a + ( r [ k ] as number ) , 0 ) ;
const themeable = sum ( 'public' ) + sum ( 'private' ) + sum ( 'global' ) + sum ( 'literal' ) ;
const sorted = [ . . . rows ] . sort ( ( a , b ) = > a . reach - b . reach || b . knobs - a . knobs ) ;
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const structural = rows . filter ( ( r ) = > STRUCTURAL_COMPONENTS . has ( r . component ) ) ;
// A structural component has no contract BY NATURE: listing it as debt here
// is the same false signal the class exists to kill.
const noContract = rows
. filter ( ( r ) = > r . contractKeys === 0 && ! STRUCTURAL_COMPONENTS . has ( r . component ) )
. map ( ( r ) = > r . component ) ;
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const bridgeRows = rows . filter ( ( r ) = > r . bridge > 0 ) . sort ( ( a , b ) = > b . bridge - a . bridge ) ;
const channelRows = rows . filter ( ( r ) = > r . channel > 0 ) . sort ( ( a , b ) = > b . channel - a . channel ) ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const worst = [ . . . rows ]
. map ( ( r ) = > ( { c : r.component , out : r.private + r . global + r . literal , r } ) )
. sort ( ( a , b ) = > b . out - a . out )
. slice ( 0 , 20 ) ;
return ` # Auditoría de alcance de tema — el catálogo entero
> * * Generado * * , no escrito a mano : \ ` node --import tsx/esm scripts/theming-census.ts --report \` .
> Este README es la vista de conjunto ; * * una ficha por componente * * al lado , con
> su an á lisis y su * * propuesta de correcci ó n * * . M é todo , clases , protocolo de
> verificaci ó n y fases : [ \ ` PLAN-theming.md \` ](../../process/PLAN-theming.md).
> La auditor í a del SISTEMA de theming ( fase 1 , cerrada 2026 - 07 - 07 ) es
> [ \ ` theming-audit.md \` ](../theming-audit.md); ésta es la deuda de adopción que
> aqu é lla dej ó apuntada en su § 5.3 - 3 .
- * * Medido * * : $ { today } · * * $ { rows . length } recetas * * con CSS + * * $ { empties . length } componentes sin receta * * = $ { rows . length + empties . length } fichas , el á rbol entero de \ ` eidos/components/ \`
- * * La pregunta * * : ¿ cu á nto de la apariencia de cada componente puede cambiar un tema * * sin tocar el sistema ni la receta * * ?
- * * Alcance global * * : * * $ { pct ( sum ( 'public' ) , themeable ) } * * — $ { sum ( 'public' ) } de $ { themeable } knobs pasan por un token p ú blico del componente
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
- * * Reparto * * : p ú blico $ { sum ( 'public' ) } · privado $ { sum ( 'private' ) } · global $ { sum ( 'global' ) } · literal $ { sum ( 'literal' ) } · sistema transversal $ { sum ( 'system' ) } · excepci ó n firmada $ { sum ( 'exception' ) } · estructural $ { sum ( 'structural' ) } · puente de paleta $ { sum ( 'bridge' ) } · canal de valor $ { sum ( 'channel' ) } _ ( los cinco ú ltimos , fuera del ratio ) _
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
- * * Sin token p ú blico propio * * : $ { noContract . length } · * * alcance < 20 % * * : $ { rows . filter ( ( r ) = > r . reach < 0.2 ) . length } · * * alcance 100 % * * : $ { rows . filter ( ( r ) = > r . reach === 1 && r . public > 0 ) . length } · * * con eje \ ` size \` **: ${ rows . filter ( ( r ) = > r . hasSize ) . length } · **estructurales**: ${ structural . length }
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
# # C ó mo se lee
| clase | qu é es | ¿ lo alcanza un tema del componente ? |
| -- - | -- - | -- - |
| \ ` public \` | \` var(--{c}-…) \` , el contrato del componente | **sí** |
| \ ` private \` | \` var(--_{c}-…) \` , nombre interno | sólo si el privado deriva de un público (cada ficha lo dice en §3) |
| \ ` global \` | primitivo del sistema ( \` --space-* \` , \` --radius-* \` , \` --color-* \` , bundle \` --size-{k}-* \` …) | sólo moviendo el sistema entero |
| \ ` literal \` | ni token: \` 8px \` , \` 1.25 \` , \` #fff \` | no — y viola R-2/R-4 |
| \ ` system \` | sistemas transversales que la receta CONSUME por contrato (capa de estado, anillo de foco, planos de depth, motion, bandas z, opacidad, shape, floating-gap) | sí, **a nivel de sistema**, por diseño (recipe-contract §2) — fuera del ratio |
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception`
Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda
resultan ser desviaciones que el canon ya había firmado.
`recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */`
EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre
(`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el
único instrumento que no: su `strip()` blanquea los comentarios ANTES de
clasificar, así que las **81 anotaciones del catálogo** contaban como literales
sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la
del que no sabía leerlo.
Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como
`system`, y por la misma razón: no es que un tema no lo alcance, es que el canon
ya aceptó por escrito que ahí no va un token.
**54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que
su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human`
16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**),
`result` 1 (86 % → **90 %**), y siete más con una cada uno.
Detalles que costaron medición:
- **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final. Se comprueban
todas las líneas que abarca la declaración.
- **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los
saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea
y basta con guardar el crudo al lado.
- Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a
`literal` (21 → 20) y el conteo vuelve al restaurarla.
Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que
las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna
parte; ahora la ficha del componente las enseña, que es donde se decide si una
sigue siendo cierta.
Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de
reparto); el README de la auditoría gana la fila de la clase y la nota de que
`system` y `exception` quedan fuera del denominador.
Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check
0 · `check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| \ ` exception \` | un literal con su anotación \` /* literal: <razón> */ \` en la propia declaración | no hace falta: es la válvula de recipe-contract §3, una desviación ya firmada — fuera del ratio |
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
| \ ` structural \` | **todos** los knobs de un componente cuyo 0 % es su NATURALEZA y no su deuda: no hay contrato que escribir (lista firmada abajo) | no hace falta: no hay superficie que un tema pueda nombrar — fuera del ratio, y su fila lee \` strct \` , no \` 0 % \` |
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
| \ ` bridge \` | \` var(--_{c}-palette-{slot}) \` : el privado que **escribe el forward** de la cascada de paleta (THM-2) | sí, **por la paleta** — y también por los tonos públicos del componente; acuñar encima mataría el \` color= \` por instancia — fuera del ratio |
| \ ` channel \` | \` var(--_{c}-x, fallback) \` que **nadie declara**: lo escribe soma o el envoltorio por INSTANCIA (un %, un rect medido, la talla de una prop) | **no debe**: fijarlo desde un tema rompe el comportamiento — fuera del ratio |
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
* * Alcance * * = \ ` public / (public + private + global + literal) \` . \` system \` ,
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
\ ` exception \` , \` structural \` , \` bridge \` y \` channel \` quedan fuera del denominador: el
primero es tematizable a nivel de sistema por dise ñ o , el segundo es una
desviaci ó n que el canon ya acept ó por escrito , el tercero no tiene nada que un
tema pueda nombrar , y los dos ú ltimos son las dos formas de privado que la
doctrina de este eje ya adjudic ó como NO deuda — el mecanismo de la paleta y el
valor de runtime . Lo que queda en \ ` private \` es el **residuo**: se adjudica clave
por clave ( \ ` theming-census.ts --residue \` ).
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* * L í mites de la medida * * ( regex sobre el CSS ; sobre - reporta , nunca infra - reporta ) :
una declaraci ó n con varios tokens se clasifica por la primera clase que casa
( p ú blico → privado → sistema → global ) ; el token p ú blico de OTRO componente
cuenta como \ ` global \` y la ficha lo marca «⤴ prestado»; los valores dentro de
\ ` @keyframes \` cuentan como cualquier otro. Tres defectos que ningún regex ve —
privado que no deriva , velo en el nodo equivocado , doble animaci ó n — van en la
checklist § 4.4 de cada ficha .
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
# # Estructurales ( $ { structural . length } ) — 0 % por naturaleza , no por deuda
Medidos uno a uno el 2026 - 08 - 22 , con * * veredicto § 5 escrito * * en su ficha :
ninguno tiene contrato que escribir . Mientras el censo los contaba como a un
componente con deuda real , la cifra global ment í a por abajo y el gate de F3
( « censo 100 % » ) era inalcanzable * * por construcci ó n * * ( next - features § 13 ) . Sus
knobs se siguen contando y listando — un knob estructural sigue siendo un knob — ;
s ó lo salen del * * denominador * * , igual que \ ` system \` . La lista firmada vive en
\ ` scripts/theming-census.ts \` ( \` STRUCTURAL_COMPONENTS \` ), con su razón al lado.
$ { structural . map ( ( r ) = > ` - [ \` ${ r . component } \` ](./ ${ r . component } .md) ( ${ r . structural } ): ${ STRUCTURAL_COMPONENTS . get ( r . component ) } ` ) . join ( '\n' ) }
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
# # Puente de paleta ( $ { sum ( 'bridge' ) } knobs ) y canal de valor ( $ { sum ( 'channel' ) } ) — privados que NO son deuda
Las dos formas de privado que la doctrina de este eje ya adjudic ó , y que ahora
se distinguen * * mec á nicamente * * — medidas contra el emisor , no adivinadas por el
nombre . La fuente es la SALIDA del generador ( \ ` src/uix/eidos/generated/base.css \` ,
que \ ` generated-css.test.ts \` mantiene idéntica a \` renderStaticCss() \` ):
- * * puente * * — el nombre aparece en la CSS generada como la l í nea del forward
\ ` --_{c}-palette-{slot}: var(--palette-{slot}, …) \` . Un nombre con forma de
puente que nadie reenv í a * * no cuenta * * .
- * * canal * * — el privado no lo declara nadie : ni la CSS del componente ni el
generador . Lo escribe soma o el envoltorio por instancia .
* * Puente * * ( $ { bridgeRows . length } ) : $ { bridgeRows . map ( ( r ) = > ` [ \` ${ r . component } \` ](./ ${ r . component } .md) ${ r . bridge } ` ) . join ( ' · ' ) }
* * Canal * * ( $ { channelRows . length } ) : $ { channelRows . map ( ( r ) = > ` [ \` ${ r . component } \` ](./ ${ r . component } .md) ${ r . channel } ` ) . join ( ' · ' ) }
Lo que NO cae en ninguna de las dos es el * * residuo * * ( $ { sum ( 'private' ) } knobs en $ { rows . filter ( ( r ) = > r . private > 0 ) . length } componentes ) :
conmutadores por variante , privados que leen un global en una rama , escaleras
por talla sin p ú blico detr á s . Se adjudican clave a clave — \ ` theming-census.ts
-- residue \ ` los vuelca con las fuentes de cada declaración.
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
# # Qu é NO propone una ficha
La propuesta deriva nombres de la doctrina ; * * no la contradice * * . Por eso una
ficha nunca propone acu ñ ar un token para :
- * * lo que posee una capa compartida * * ( \ ` list-surface \` , \` spin-field \` ,
\ ` picker-shell \` , \` viewport-placement \` , \` field-segment-state \` ,
\ ` menu-indicator \` , \` sliding-indicator \` ): un eje = un token público de la capa
+ una ranura privada , consumido \ ` var(--_x, var(--x)) \` — el consumidor no acuña
\ ` --{c}-{eje} \` (README de \` eidos/components \` , «Capas compartidas» regla 2);
- * * el foco * * ( \ ` --focus-ring-* \` , theming §32) ni **la capa de estado**
( \ ` --state-* \` , §38): un knob del sistema, no del componente;
- * * un token prestado de otro componente * * ( la familia \ ` calendar \` en los
pickers ) : la correcci ó n es la decisi ó n de familia registrada en
[ \ ` theming-audit.md \` ](../theming-audit.md) §B, no un duplicado con prefijo propio;
- * * un shorthand o un eje f í sico * * ( \ ` padding \` , \` border \` , \` height \` ): primero se
parte en ejes l ó gicos ( recipe - contract § 1 , R - 4.4 ) , y eso es decisi ó n .
# # Sin receta CSS ( $ { empties . length } )
Ni un \ ` .css \` en su directorio: **no hay knob que medir**, y eso es una
respuesta , no un hueco . Cada ficha dice — medido , no supuesto — d ó nde vive su
visual : el componente que compone , la capa compartida que consume , o la receta
ajena que pinta sus attrs ( y cuyos knobs ya est á n contados all í ) .
$ { empties . map ( ( e ) = > ` [ \` ${ e . component } \` ](./ ${ e . component } .md) ` ) . join ( ' · ' ) }
Y el * * vocabulario de nombres tiene una desviaci ó n medida * * : el cat á logo habla
mayoritariamente con el modificador al final ( \ ` -bg-hover \` , \` -bg-active \` ) mientras
theming § 6.7 lo pone delante ( \ ` hover-bg \` ). Las fichas proponen la forma
* * documentada * * y lo dejan anotado : la normalizaci ó n es * * D - TH . 6 * * , sin firmar .
# # Los 20 con m á s knobs fuera del contrato
| # | componente | fuera de alcance | alcance | knobs | contrato |
| -- - : | -- - | -- - : | -- - : | -- - : | -- - : |
$ { worst . map ( ( w , i ) = > ` | ${ i + 1 } | [ ${ w . c } ](./ ${ w . c } .md) | ${ w . out } | ${ reachOf ( w . r ) } | ${ w . r . knobs } | ${ w . r . contractKeys } | ` ) . join ( '\n' ) }
# # Sin ning ú n token p ú blico propio ( $ { noContract . length } )
Ni una clave p ú blica en \ ` lib/recipes/base.ts \` — alguno tiene privados forward
( \ ` _palette-* \` ), que no son contrato: un tema no puede nombrarlos.
$ { noContract . map ( ( c ) = > ` [ \` ${ c } \` ](./ ${ c } .md) ` ) . join ( ' · ' ) }
# # Tabla completa ( $ { rows . length } recetas , por alcance ascendente )
La columna « contrato » cuenta las claves * * p ú blicas * * del bloque del componente .
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
| componente | alcance | knobs | p ú blico | privado | global | literal | sistema | estructural | puente | canal | contrato | size |
| -- - | -- - : | -- - : | -- - : | -- - : | -- - : | -- - : | -- - : | -- - : | -- - : | -- - : | -- - : | : - : |
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
$ { sorted
. map (
( r ) = >
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
` | [ ${ r . component } ](./ ${ r . component } .md) | ${ reachOf ( r ) } | ${ r . knobs } | ${ r . public } | ${ r . private } | ${ r . global } | ${ r . literal } | ${ r . system } | ${ r . structural } | ${ r . bridge } | ${ r . channel } | ${ r . contractKeys } | ${ r . hasSize ? 'y' : '– ' } | `
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
)
. join ( '\n' ) }
# # Veredicto y decisiones
$ { verdict }
` ;
}
function readVerdict ( path : string ) : string {
if ( ! existsSync ( path ) ) return VERDICT_SEED ;
const prev = readFileSync ( path , 'utf8' ) ;
const a = prev . indexOf ( MARK_START ) ;
const b = prev . indexOf ( MARK_END ) ;
if ( a < 0 || b <= a ) return VERDICT_SEED ;
return prev . slice ( a , b + MARK_END . length ) . replace ( /\r\n/g , '\n' ) ;
}
/ * *
* HAND - WRITTEN PROSE SURVIVES REGENERATION — anywhere , not just the verdict .
*
* The report rewrites the whole sheet from the census , so an elaboration a
* human added inside § 1.1 – § 1.4 was silently replaced by the template : where
* someone had written « _Ninguno_ — los cuatro que hab í a ( … ) est á n cosidos » ,
* regeneration left « _Ninguno . _ » . It happened TWICE in one day to the two
* ` chat-* ` sheets ( 2026 - 08 - 24 ) . Mostly - harmless - occasionally - destructive is
* the worst possible split , because nobody reads the diff .
*
* The contract : wrap hand - written prose in ` <!-- mano:start --> ` …
* ` <!-- mano:end --> ` and it is re - inserted after the SAME heading it sat
* under . A block whose heading no longer exists is appended under a
* « rescatado » note rather than dropped — losing prose is never the default .
* /
const HAND_START = '<!-- mano:start -->' ;
const HAND_END = '<!-- mano:end -->' ;
type HandBlock = { heading : string ; body : string } ;
function readHandBlocks ( path : string ) : HandBlock [ ] {
if ( ! existsSync ( path ) ) return [ ] ;
const prev = readFileSync ( path , 'utf8' ) . replace ( /\r\n/g , '\n' ) ;
const out : HandBlock [ ] = [ ] ;
let from = 0 ;
for ( ; ; ) {
const a = prev . indexOf ( HAND_START , from ) ;
if ( a < 0 ) break ;
const b = prev . indexOf ( HAND_END , a ) ;
if ( b < 0 ) break ;
const headings = prev . slice ( 0 , a ) . match ( /^#{2,4} .*$/gm ) ;
out . push ( {
heading : headings ? headings [ headings . length - 1 ] : '' ,
body : prev.slice ( a , b + HAND_END . length )
} ) ;
from = b + HAND_END . length ;
}
return out ;
}
function spliceHandBlocks ( sheet : string , blocks : HandBlock [ ] ) : string {
if ( ! blocks . length ) return sheet ;
let out = sheet ;
const orphans : string [ ] = [ ] ;
for ( const { heading , body } of blocks ) {
if ( out . includes ( body ) ) continue ;
const i = heading ? out . indexOf ( heading + '\n' ) : - 1 ;
if ( i < 0 ) {
orphans . push ( body ) ;
continue ;
}
const cut = i + heading . length + 1 ;
out = out . slice ( 0 , cut ) + '\n' + body + '\n' + out . slice ( cut ) ;
}
if ( orphans . length )
out +=
'\n## Prosa rescatada\n\n' +
'> Estos bloques estaban bajo un encabezado que ya no existe.\n' +
'> Reubicalos o borralos a mano.\n\n' +
orphans . join ( '\n\n' ) +
'\n' ;
return out ;
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
function writeReport() {
mkdirSync ( REPORT_DIR , { recursive : true } ) ;
const today = new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ;
const scans : Scan [ ] = [ ] ;
const empties : EmptyScan [ ] = [ ] ;
for ( const dir of readdirSync ( ROOT ) ) {
const scan = scanComponent ( dir ) ;
if ( scan ) {
scans . push ( scan ) ;
continue ;
}
const empty = scanEmpty ( dir ) ;
if ( empty ) empties . push ( empty ) ;
}
for ( const scan of scans ) {
const path = join ( REPORT_DIR , ` ${ scan . row . component } .md ` ) ;
const hand = readHandBlocks ( path ) ;
writeFileSync ( path , spliceHandBlocks ( sheet ( scan , today , readVerdict ( path ) ) , hand ) , 'utf8' ) ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
}
for ( const empty of empties ) {
const path = join ( REPORT_DIR , ` ${ empty . component } .md ` ) ;
const handEmpty = readHandBlocks ( path ) ;
writeFileSync ( path , spliceHandBlocks ( emptySheet ( empty , today , readVerdict ( path ) ) , handEmpty ) , 'utf8' ) ;
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
}
const readmePath = join ( REPORT_DIR , 'README.md' ) ;
writeFileSync ( readmePath , rootReadme ( scans , empties , today , readVerdict ( readmePath ) ) , 'utf8' ) ;
console . log (
` wrote docs/audit/theming/README.md + ${ scans . length } recipe sheets + ${ empties . length } no-recipe sheets `
) ;
}
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// ─── `--names`: the naming grammar (D-TH.6, signed 2026-08-20) ───────────────
/ * *
* The signed grammar , as data . Three families , three shapes :
*
* role / palette ` {part-}?{role|palette}-{COLOR_ROLE_SLOT} ` modifier BEHIND
* ( ` primary-solid-hover ` ) — canonical BY CONSTRUCTION , the
* slot inventory itself carries ` hover ` / ` active ` / ` solid-hover ` .
* system ` --state-* ` , ` --opacity-* ` , ` --focus-ring-* ` — not a recipe
* key ; out of this classifier ' s reach .
* recipe - level ` [variant-]?[modifier-]?[part-]?{slot}[-{size}] ` — modifier
* IN FRONT ( theming § 6.7 r7 ) : " delante lo interactivo , detr á s
* lo dimensional y contextual " .
*
* The ink slot is ` fg ` ; ` color ` as a slot is dead ( 2026 - 08 - 20 ) .
*
* WHAT MOVES TO THE FRONT is the INTERACTIVE state , and only it — the closed
* universal vocabulary below ( ` current ` included : it is an ARIA state ) . A
* qualifier that is contextual ( ` meter.indicator-bg-below ` , ` progress ` ' s
* ` loaded ` ) , orientational ( ` radio-group.gap-vertical ` ) or modal
* ( ` sidebar.width-icon ` ) stays BEHIND : that is the other half of the signed
* sentence , and promoting it would rewrite ` --sidebar-width-icon ` ( the width
* of the icon - collapsed rail ) into ` --sidebar-icon-width ` ( the width of an
* icon ) , which is a different thing . The morfo ' s declared values are the
* source for VALIDATING that trailing qualifier , never for promoting it .
* Where the author wanted a specific promotion , it is a signed name in
* ` NAME_OVERRIDE ` , never a rule that generalises silently .
* /
const UNIVERSAL_MODIFIERS = [
'hover' ,
'active' ,
'selected' ,
'disabled' ,
'checked' ,
'open' ,
'focus' ,
'invalid' ,
'current'
] ;
/** Colour-role slots as EMITTED (kebab) — `COLOR_ROLE_SLOTS` + the generated extras. */
const ROLE_SLOT_TAILS = [
'solid-hover' ,
'surface-hover' ,
'text-strong' ,
'hover' ,
'active' ,
'text' ,
'solid' ,
'surface' ,
'element' ,
'track' ,
'bg2' ,
'separator' ,
'border' ,
'contrast'
] ;
const ROLES = [
'primary' ,
'secondary' ,
'tertiary' ,
'neutral' ,
'affirm' ,
'fulfill' ,
'risk' ,
'threat' ,
'loss' ,
'palette'
] ;
/** Recipe-level variant archetypes that carry their own `-hover`/`-active` pair. */
const VARIANT_TAILS = /(^|-)(solid|surface|soft|outline|ghost|subtle|element)-(hover|active)$/ ;
/ * *
* The three false - friend groups the author signed as KEEPING their ` color `
* segment ( 2026 - 08 - 20 acta ) : the system ' s focus - ring family , colour as a NOUN ,
* and ` stop-color ` as a PART name .
fix(theming)!: avatar - el contrato dice la verdad: 4 claves, cero desviadas
Las ultimas CUATRO claves fuera de gramatica de las 4.558 publicas del
catalogo, resueltas cada una por lo que ES (no eran cuatro iguales):
- `badge-color-custom-contrast` -> `badge-fg-custom-contrast`: la unica TINTA
real y alcanzable. Nombre derivado DE la gramatica D-TH.6 (la ranura se
sustituye EN SU SITIO; `custom` no es interactivo ni rol, va detras) con el
precedente firmado scrim-fg-over-dark - y es la salida literal del
clasificador que consume R-5.3. Renombre a mano (el codemod ABORTA sobre la
IIFE de avatar - deuda registrada en 13): value-preserving, verificado con
sonda dirigida (la clave vieja movia antes y no despues; la nueva al reves).
- `ring-color-custom` y `badge-color-custom` RETIRADAS del contrato publico:
canales de valor INALCANZABLES (puerta data-*-custom y escritura inline
nacen de la misma expresion - mentiras bajo R-5.4). Mecanismo: canal
privado de envoltorio `--_avatar-*-color-custom` + respaldo currentColor
(el patron de las 5 privadas-de-envoltorio del parque, 5/5 sin declaracion
en receta), NO borrado (el CSS las lee) NI privada plana (cero precedente).
Ley 8.bis re-aplicada: cero lectores fuera de avatar, re-barrido.
- `group-carve-color` EXENTA en NAME_EXEMPT con razon medida: alimenta
box-shadow (geometria del carve, no tinta; alcanza en 59 instancias) -
`fg` seria gramatica correcta con semantica peor.
Medido: sonda 7.4 0/576 - pixel visible IDENTICO (anillo ambar, insignia
violeta, tinta white) e instancia imposible identica (currentColor preserva
el default retirado) - Tokens 88 -> 86 EN VIVO con el nombre nuevo (captura
revisada por el supervisor) - censo --names: DESVIADAS 0 EN 0 COMPONENTES
(4.556 claves) - centinela exit 0 - lint 0 - docs-check 0/0 - vitest 442+1
ajeno - check 0 atribuibles.
Hallazgo de instrumento NUEVO, adjudicado y registrado en 13: el chooser del
centinela elige el valor por NOMBRE y no conoce `fg` MEDIAL (su prueba de
tinta es `fg$`): las 5 claves que la propia D-TH.6 fabrica (badge-fg-custom-
contrast, bubble-fg-in/-out, scrim-fg-over-dark/-light) reciben 1234px,
invalido para color - y el veredicto depende de si el respaldo coincide por
azar (bubble-fg-* leen vivas POR ACCIDENTE). Arreglarlo obliga a re-verificar
el ledger - firma de instrumento aparte.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
*
* The fourth ( 2026 - 08 - 25 acta ) is of a different kind : not a false friend but a
* measured one . The classifier decides by the FORM of the name ; ` group-carve-color `
* is the one key whose DESTINATION contradicts it .
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* /
const NAME_EXEMPT : Array < { re : RegExp ; why : string } > = [
{ re : /focus-ring-color$/ , why : 'espeja la familia del SISTEMA --focus-ring-*' } ,
{ re : /^orb-color-/ , why : 'color como sustantivo del orbe, no slot' } ,
fix(theming)!: avatar - el contrato dice la verdad: 4 claves, cero desviadas
Las ultimas CUATRO claves fuera de gramatica de las 4.558 publicas del
catalogo, resueltas cada una por lo que ES (no eran cuatro iguales):
- `badge-color-custom-contrast` -> `badge-fg-custom-contrast`: la unica TINTA
real y alcanzable. Nombre derivado DE la gramatica D-TH.6 (la ranura se
sustituye EN SU SITIO; `custom` no es interactivo ni rol, va detras) con el
precedente firmado scrim-fg-over-dark - y es la salida literal del
clasificador que consume R-5.3. Renombre a mano (el codemod ABORTA sobre la
IIFE de avatar - deuda registrada en 13): value-preserving, verificado con
sonda dirigida (la clave vieja movia antes y no despues; la nueva al reves).
- `ring-color-custom` y `badge-color-custom` RETIRADAS del contrato publico:
canales de valor INALCANZABLES (puerta data-*-custom y escritura inline
nacen de la misma expresion - mentiras bajo R-5.4). Mecanismo: canal
privado de envoltorio `--_avatar-*-color-custom` + respaldo currentColor
(el patron de las 5 privadas-de-envoltorio del parque, 5/5 sin declaracion
en receta), NO borrado (el CSS las lee) NI privada plana (cero precedente).
Ley 8.bis re-aplicada: cero lectores fuera de avatar, re-barrido.
- `group-carve-color` EXENTA en NAME_EXEMPT con razon medida: alimenta
box-shadow (geometria del carve, no tinta; alcanza en 59 instancias) -
`fg` seria gramatica correcta con semantica peor.
Medido: sonda 7.4 0/576 - pixel visible IDENTICO (anillo ambar, insignia
violeta, tinta white) e instancia imposible identica (currentColor preserva
el default retirado) - Tokens 88 -> 86 EN VIVO con el nombre nuevo (captura
revisada por el supervisor) - censo --names: DESVIADAS 0 EN 0 COMPONENTES
(4.556 claves) - centinela exit 0 - lint 0 - docs-check 0/0 - vitest 442+1
ajeno - check 0 atribuibles.
Hallazgo de instrumento NUEVO, adjudicado y registrado en 13: el chooser del
centinela elige el valor por NOMBRE y no conoce `fg` MEDIAL (su prueba de
tinta es `fg$`): las 5 claves que la propia D-TH.6 fabrica (badge-fg-custom-
contrast, bubble-fg-in/-out, scrim-fg-over-dark/-light) reciben 1234px,
invalido para color - y el veredicto depende de si el respaldo coincide por
azar (bubble-fg-* leen vivas POR ACCIDENTE). Arreglarlo obliga a re-verificar
el ledger - firma de instrumento aparte.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
{ re : /^stop-color(-|$)/ , why : 'stop-color ES la parte (gradient-builder-stop-color.svelte)' } ,
{
re : /^group-carve-color$/ ,
why : 'alimenta box-shadow (la GEOMETRÍA del carve, no tinta): avatar.css `[data-carve-out] { box-shadow: 0 0 0 var(--avatar-group-carve-width) var(--avatar-group-carve-color) }`. Alcanza — medido en las 59 instancias de /uix/components/avatar-group (oklch(0.9911 0 0) → rgb(1,2,3), restaurado). `group-carve-fg` sería gramática correcta y semántica PEOR: nombraría tinta lo que es el filo que recorta la silueta contra el avatar anterior'
}
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
] ;
/ * *
* Destinations the author decided by NAME in the 2026 - 08 - 20 acta , where the
* generic grammar cannot deduce them . Each one is a reading of the component ,
* not a rule : ` on-dark ` reads as the canonical ` on- ` accent prefix and had to
* move away from it ; ` played ` / ` buffered ` are waveform REGIONS ( pseudo - parts ) ,
* so they lead like a part ; ` inactive ` IS rating - group ' s resting state , and a
* resting state carries no modifier .
* /
const NAME_OVERRIDE : Record < string , string > = {
'background.scrim-color-on-dark' : 'scrim-fg-over-dark' ,
'background.scrim-color-on-light' : 'scrim-fg-over-light' ,
'waveform.color-played' : 'played-fg' ,
'waveform.color-buffered' : 'buffered-fg' ,
'rating-group.item-color-inactive' : 'item-fg' ,
// Estados de ITEM que el autor nombró uno a uno: son interactivos/de estatus
// y van delante, pero no salen de una regla — salen de su firma.
'rating-group.item-color-active' : 'active-item-fg' ,
'rating-group.item-color-partial' : 'partial-item-fg' ,
'chat-message.status-color-read' : 'read-status-fg' ,
'chat-message.status-color-failed' : 'failed-status-fg'
} ;
export type NameClass =
| 'ok' // already speaks the grammar
| 'role-slot' // canonical by construction — never touch
| 'exempt' // signed false friend
| 'state-layer' // neutral hover: MIGRATES (firma 3), not renamed
| 'ink' // `color` as slot → `fg`
| 'modifier' // modifier behind → in front
| 'both' ;
export interface NameFinding {
component : string ;
key : string ;
klass : NameClass ;
to? : string ;
why? : string ;
}
/** Values every `data-*` axis of the component's morfo declares (lowercased). */
async function morfoModifiers ( component : string ) : Promise < Set < string > > {
const out = new Set < string > ( ) ;
const path = resolve ( ` src/uix/morfo/components/ ${ component } .ts ` ) ;
if ( ! existsSync ( path ) ) return out ;
try {
const mod = ( await import ( ` ../src/uix/morfo/components/ ${ component } ` ) ) as Record <
string ,
unknown
> ;
const key = ` ${ component . replace ( /-([a-z])/g , ( _ , c : string ) = > c . toUpperCase ( ) ) } Morfo ` ;
const morfo = mod [ key ] ;
if ( ! morfo ) return out ;
const compiled = compileMorfo ( morfo as Morfo ) ;
for ( const contracts of compiled . contracts . dataAttrsByPart . values ( ) )
for ( const c of contracts ) for ( const v of c . values ? ? [ ] ) out . add ( v . toLowerCase ( ) ) ;
} catch {
/* a morfo that will not import is the eidos-lint's business, not the census's */
}
return out ;
}
fix(eidos): la capa de estado es un VELO — 38 hovers que no lo eran, al idioma
El codemod de ayer apartó 47 knobs «en cola de migración a la capa de estado».
Al empezar esa migración se ve que sólo SEIS lo estaban: el velo de §38 es
`background-image: linear-gradient(var(--state-hover), var(--state-hover))` y
no alcanza nada más. De los otros 41, veintidós mueven un BORDE y diecinueve
mueven la TINTA — ninguno es la capa de estado por muy neutro que sea su valor.
Eran deuda de nombre, y yo los había excluido del renombrado.
El clasificador tenía media prueba: miraba el VALOR (¿neutro o con valencia?) y
no la PROPIEDAD. Ahora exige las dos, siguiendo un salto por privado
(`--_switch-track-bg-hover`) y entre componentes (la familia calendar presta
`--calendar-control-*` a month-grid, range-calendar y year-grid, y por eso
esos cuatro no aparecían consumidos en su propia receta).
renombres 38 en 22 componentes · 113 referencias en 30 ficheros
cola real 6 (checkbox · date-range-picker ×2 · scroll-area · splitter ·
switch) — los únicos que pintan fondo
censo 162 · 5.203 · 1.841 (37 %) · 56 — IDÉNTICO
--names 38 → 0 desviadas
diff generated 38 renombres 1:1 · 0 cambios de valor
computed field · tabs contra la línea base ORIGINAL (anterior a las dos
pasadas): 4.031 valores, 16 estados, 0 diffs
huérfanos 0 · formato sin regresión · audit 163 PASS · rtl 0 · docs 0 ·
suite 434 pasan (1 rojo conocido) · check 0 errores en ficheros tocados
La muta-prueba se reescribió contra el catálogo vivo — asertaba nombres que el
codemod ya había renombrado — y gana los casos que faltaban: un knob de borde
neutro es `modifier`, uno de tinta neutro es `both`, y sólo los de fondo son
`state-layer`.
Queda una pregunta que §38 no contesta y que NO he decidido: si un hover de
borde o de tinta POR COMPONENTE debe existir siquiera. R-4.3 guarda
`background*` en `:hover`, nada más. Ahora al menos se llaman como deben
mientras se decide.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/** Every component recipe CSS, read once — a token may be consumed by a SIBLING. */
let cssIndex : Array < { component : string ; text : string } > | null = null ;
function allComponentCss ( ) : Array < { component : string ; text : string } > {
if ( cssIndex ) return cssIndex ;
cssIndex = [ ] ;
for ( const dir of readdirSync ( ROOT ) ) {
const path = join ( ROOT , dir , ` ${ dir } .css ` ) ;
if ( ! existsSync ( path ) ) continue ;
cssIndex . push ( { component : dir , text : readFileSync ( path , 'utf8' ) . replace ( /\r\n/g , '\n' ) } ) ;
}
return cssIndex ;
}
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/ * *
fix(eidos): la capa de estado es un VELO — 38 hovers que no lo eran, al idioma
El codemod de ayer apartó 47 knobs «en cola de migración a la capa de estado».
Al empezar esa migración se ve que sólo SEIS lo estaban: el velo de §38 es
`background-image: linear-gradient(var(--state-hover), var(--state-hover))` y
no alcanza nada más. De los otros 41, veintidós mueven un BORDE y diecinueve
mueven la TINTA — ninguno es la capa de estado por muy neutro que sea su valor.
Eran deuda de nombre, y yo los había excluido del renombrado.
El clasificador tenía media prueba: miraba el VALOR (¿neutro o con valencia?) y
no la PROPIEDAD. Ahora exige las dos, siguiendo un salto por privado
(`--_switch-track-bg-hover`) y entre componentes (la familia calendar presta
`--calendar-control-*` a month-grid, range-calendar y year-grid, y por eso
esos cuatro no aparecían consumidos en su propia receta).
renombres 38 en 22 componentes · 113 referencias en 30 ficheros
cola real 6 (checkbox · date-range-picker ×2 · scroll-area · splitter ·
switch) — los únicos que pintan fondo
censo 162 · 5.203 · 1.841 (37 %) · 56 — IDÉNTICO
--names 38 → 0 desviadas
diff generated 38 renombres 1:1 · 0 cambios de valor
computed field · tabs contra la línea base ORIGINAL (anterior a las dos
pasadas): 4.031 valores, 16 estados, 0 diffs
huérfanos 0 · formato sin regresión · audit 163 PASS · rtl 0 · docs 0 ·
suite 434 pasan (1 rojo conocido) · check 0 errores en ficheros tocados
La muta-prueba se reescribió contra el catálogo vivo — asertaba nombres que el
codemod ya había renombrado — y gana los casos que faltaban: un knob de borde
neutro es `modifier`, uno de tinta neutro es `both`, y sólo los de fondo son
`state-layer`.
Queda una pregunta que §38 no contesta y que NO he decidido: si un hover de
borde o de tinta POR COMPONENTE debe existir siquiera. R-4.3 guarda
`background*` en `:hover`, nada más. Ahora al menos se llaman como deben
mientras se decide.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* The CSS properties a token feeds , following ONE hop through a private
* ( ` --_switch-track-bg-hover: var(--switch-track-bg-off-hover) ` ) and across
* components ( the calendar family lends ` --calendar-control-* ` to month - grid ,
* range - calendar and year - grid ) .
* /
function propertiesFed ( token : string ) : string [ ] {
const props : string [ ] = [ ] ;
const privates : string [ ] = [ ] ;
for ( const { text } of allComponentCss ( ) ) {
if ( ! text . includes ( token ) ) continue ;
for ( const line of text . split ( '\n' ) ) {
if ( ! line . includes ( token ) ) continue ;
const m = line . match ( /^\s*(--[a-z0-9_-]+|[a-z-]+)\s*:/ ) ;
if ( ! m ) continue ;
if ( m [ 1 ] . startsWith ( '--' ) ) privates . push ( m [ 1 ] ) ;
else props . push ( m [ 1 ] ) ;
}
}
for ( const priv of privates )
for ( const { text } of allComponentCss ( ) ) {
if ( ! text . includes ( priv ) ) continue ;
for ( const line of text . split ( '\n' ) ) {
if ( ! line . includes ( priv ) ) continue ;
const m = line . match ( /^\s*([a-z-]+)\s*:/ ) ;
if ( m ) props . push ( m [ 1 ] ) ;
}
}
return props ;
}
/ * *
* A knob heads for the state - layer migration ( § 38 + R - 4.3 ) only when it is
* BOTH neutral in value AND painting a BACKGROUND — the veil of § 38 is
* ` background-image: linear-gradient(var(--state-hover), var(--state-hover)) `
* and it reaches nothing else .
*
* Measured 2026 - 08 - 20 , and it corrected this classifier : of the 47 knobs the
* value test alone had queued , only SIX paint a background . Twenty - two move a
* BORDER and nineteen move the INK — neither is the state layer , whatever
* their value , so they are ordinary naming debt and the codemod renames them .
* Whether a per - component border / ink hover should exist AT ALL is a separate
* question that § 38 does not settle ( R - 4.3 guards ` background* ` only ) .
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
*
fix(eidos): la capa de estado es un VELO — 38 hovers que no lo eran, al idioma
El codemod de ayer apartó 47 knobs «en cola de migración a la capa de estado».
Al empezar esa migración se ve que sólo SEIS lo estaban: el velo de §38 es
`background-image: linear-gradient(var(--state-hover), var(--state-hover))` y
no alcanza nada más. De los otros 41, veintidós mueven un BORDE y diecinueve
mueven la TINTA — ninguno es la capa de estado por muy neutro que sea su valor.
Eran deuda de nombre, y yo los había excluido del renombrado.
El clasificador tenía media prueba: miraba el VALOR (¿neutro o con valencia?) y
no la PROPIEDAD. Ahora exige las dos, siguiendo un salto por privado
(`--_switch-track-bg-hover`) y entre componentes (la familia calendar presta
`--calendar-control-*` a month-grid, range-calendar y year-grid, y por eso
esos cuatro no aparecían consumidos en su propia receta).
renombres 38 en 22 componentes · 113 referencias en 30 ficheros
cola real 6 (checkbox · date-range-picker ×2 · scroll-area · splitter ·
switch) — los únicos que pintan fondo
censo 162 · 5.203 · 1.841 (37 %) · 56 — IDÉNTICO
--names 38 → 0 desviadas
diff generated 38 renombres 1:1 · 0 cambios de valor
computed field · tabs contra la línea base ORIGINAL (anterior a las dos
pasadas): 4.031 valores, 16 estados, 0 diffs
huérfanos 0 · formato sin regresión · audit 163 PASS · rtl 0 · docs 0 ·
suite 434 pasan (1 rojo conocido) · check 0 errores en ficheros tocados
La muta-prueba se reescribió contra el catálogo vivo — asertaba nombres que el
codemod ya había renombrado — y gana los casos que faltaban: un knob de borde
neutro es `modifier`, uno de tinta neutro es `both`, y sólo los de fondo son
`state-layer`.
Queda una pregunta que §38 no contesta y que NO he decidido: si un hover de
borde o de tinta POR COMPONENTE debe existir siquiera. R-4.3 guarda
`background*` en `:hover`, nada más. Ahora al menos se llaman como deben
mientras se decide.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* The value test still runs first : a VALENCED hover is the recipe ' s palette
* swap ( recipe - contract § 2 ) , legitimate and renamed like any other key —
* ` dropdown-menu.item-bg-hover ` reads ` var(--color-primary-element) ` and only
* its NAME looks neutral .
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* /
function isNeutralHover ( component : string , key : string ) : boolean {
if ( ! /(^|-)hover(-|$)/ . test ( key ) ) return false ;
if ( VARIANT_TAILS . test ( key ) ) return false ;
const block = recipeBlock ( component ) ;
if ( block === null ) return false ;
const decl = block . match ( new RegExp ( "^\\t\\t'?" + key + "'?\\s*:\\s*(.+)$" , 'm' ) ) ;
const value = decl ? . [ 1 ] ? ? '' ;
fix(scripts): la estela de M0 — habia OCHO agujas ciegas al corte de Prettier, no una
M0 (`830cd7671`) curo cinco agujas del censo de theming y su cuerpo dejo tres
cabos anotados. Al tirar de ellos aparece que el defecto era mucho mas ancho:
un barrido de `scripts/` entero, con TRES ortografias de la aguja (literal
`var\(--`, cadena `'var\\(--'` y grupo `var\((--`), encuentra SEIS mas. Se
corrigen cinco y se declara la sexta.
POR QUE IMPORTA MAS QUE EN EL CENSO. La aguja del censo produce una MALA
CLASIFICACION, y una mala clasificacion al menos se cuenta en algun sitio.
Estas producen sobre todo FALSOS NEGATIVOS: el guard deja de mirar y PASA en
silencio. Cinco de las seis son de ese sentido.
LAS AGUJAS (todas con CERO victimas hoy, medido antes de tocar; se corrigen por
la misma razon que M0 escribio para las suyas sin victima: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa):
- `theming-census.ts:1267` `scopeFor` — LA DE CLASE VIVA, y esta EN EL FICHERO
QUE M0 DECLARO CURADO, comiendo de la MISMA tuberia (`knob.value`,
normalizado a espacios simples en :579). Escribiria alcance `root` donde la
verdad es `host`. Medido el catalogo: 5187 knobs + 1497 privados, 12 valores
llevan hoy `var( --` y ninguno contiene `palette-`.
- `theming-census.ts:2244` `valenced` y `:2246` el rol neutro — el segundo mata
el clasificador `state-layer` entero: sin match, el knob nunca se encola.
Su entrada es `base.ts`, que NADIE normaliza, y el lector de PRODUCCION de
esos mismos valores ya es tolerante (`render-css.ts` VAR_REF_REGEX).
- `theming-sentinel.ts:867` `kindOfTerm` y `:884` los operandos de
`calc/min/max/clamp` — el segundo es el caso que el propio comentario de
arriba documenta (22 longitudes leidas como numeros). Probado por
EQUIVALENCIA sobre las 4566 entradas de base.ts: 0 diferencias de kind ni de
sentinel entre HEAD y la version tolerante.
- `component-audit.ts:957 / :1137 / :1329` (R-2.6, R-4.3, R-4.6) — la 4.3 era
la peor del lote: opera sobre un valor SIN normalizar, tomado directo del
fuente, y su error se INVIERTE (falso positivo: acusa de «capa de estado
artesanal» a una declaracion que lee la del sistema).
- `__statelayer-analysis.ts:28` — NO TOCADA y declarada: su lector es POR
LINEA, asi que un corte trunca el valor ANTES de que la aguja corra. Anadir
tolerancia daria falsa sensacion de cobertura mientras la limitacion real
sigue en pie. (De paso, defecto preexistente NO tocado en :32: un
`new RegExp` con comillas DOBLES hace que su `\s` sea solo `s` y esa regex no
case nunca.)
`\s*` va a AMBOS lados donde la aguja cierra parentesis, porque el corte produce
`var( --x )`; solo a la izquierda donde no lo cierra. La tolerancia esta COPIADA
del hermano de la casa (`recipe-css-contract.test.ts:150`), no inventada.
DOS CORRECCIONES DOCUMENTALES que el pase adversarial caza:
- `component-audit.ts` ≈:1154 justificaba no usar `/g` con un mecanismo FALSO
(«lastIndex viaja entre llamadas»). Medido: un literal inline se re-crea en
cada evaluacion (`true,true,true`) y sobrevive a la bandera POR ACCIDENTE de
donde esta escrito; el arrastre es del OBJETO regex izado a const de modulo
(`true,FALSE,true`). La decision de no usar `/g` sigue siendo buena y ahora
se sostiene en su razon verdadera. El acta del censo queda como esta: sus
agujas SI estan izadas y alli el arrastre es real.
- `theming-census-debt.ts:58-59` se contradecia con su propia linea :96 tras
la reescritura de la caveat: `87 literal` -> `86` y `583 of the 1154 keys` ->
`582 of the 1150`. Verificado contando el ledger (palabras 272+86+1=359 ·
chronos 198+11+14=223 · 582 de 1150) y con `--debt` («1150 registered, 0 new,
0 stale»). La aritmetica cierra con las actas del propio fichero:
1154 −2 (`8ac23047f`) = 1152 −2 (`830cd7671`) = 1150.
CAVEAT DEL LEDGER reescrita: las claves `--palabras-*` ya NO estan «EN
CUESTION» — la clasificacion esta ADJUDICADA (canal de valor del scheme del
documento) y lo que queda pendiente de firma es la EJECUCION, no el veredicto.
Se anade el dato que faltaba y que cambia como se lee el numero: de los 117
nombres consumidos, el esquema por defecto emite 74 y **43 no los emite nunca**,
de modo que para esos el fallback del CSS ES el valor de diseno. (La particion
que yo habia dado, 75/42, era la resta equivocada: suponia que lo emitido es
subconjunto de lo consumido, y `--palabras-scheme-mark-highlight-bg` se emite y
ninguna receta lo lee.)
FICHAS NO REGENERADAS a proposito: `--only` se parsea pero `writeReport()` lo
ignora, asi que regenerar reescribiria las 171 fichas + README — 172 ficheros
mezclando todos los carriles. Las de split-button y palabras quedan desfasadas
hasta que alguien corra `--report` sobre un arbol quieto.
VERIFICADO: censo antes/despues byte a byte IDENTICO · `component:audit`
PASS 161 / NEEDS-WORK 5 / BROKEN 0, con `tmp/component-audit.md` byte a byte
salvo la linea `Date:` autogenerada · `theming-reach-floor` 5/5 verde · ningun
fichero bajo `web/` tocado (regla del autor: ese arbol se reconstruye).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// `\s*` after the paren, and on BOTH sides where the needle closes the paren:
// same fix as `classify` (ACTA above). The input here is a `base.ts` recipe
// value, which prettier cannot break — it lives inside a string literal — so
// there are no victims today; corrected because nothing NORMALIZES base.ts
// either, and the house's own reader of these same values is already tolerant
// (`inferDepsFromValue`, `render-css.ts:2477`). A miss here stops the
// state-layer classifier LOOKING, which passes in silence.
const valenced = new RegExp ( 'var\\(\\s*--color-(' + ROLES . join ( '|' ) + ')-' ) . test ( value ) ;
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( valenced ) return false ;
fix(scripts): la estela de M0 — habia OCHO agujas ciegas al corte de Prettier, no una
M0 (`830cd7671`) curo cinco agujas del censo de theming y su cuerpo dejo tres
cabos anotados. Al tirar de ellos aparece que el defecto era mucho mas ancho:
un barrido de `scripts/` entero, con TRES ortografias de la aguja (literal
`var\(--`, cadena `'var\\(--'` y grupo `var\((--`), encuentra SEIS mas. Se
corrigen cinco y se declara la sexta.
POR QUE IMPORTA MAS QUE EN EL CENSO. La aguja del censo produce una MALA
CLASIFICACION, y una mala clasificacion al menos se cuenta en algun sitio.
Estas producen sobre todo FALSOS NEGATIVOS: el guard deja de mirar y PASA en
silencio. Cinco de las seis son de ese sentido.
LAS AGUJAS (todas con CERO victimas hoy, medido antes de tocar; se corrigen por
la misma razon que M0 escribio para las suyas sin victima: dejarlas ciegas deja
el bug armado para la proxima linea que Prettier rompa):
- `theming-census.ts:1267` `scopeFor` — LA DE CLASE VIVA, y esta EN EL FICHERO
QUE M0 DECLARO CURADO, comiendo de la MISMA tuberia (`knob.value`,
normalizado a espacios simples en :579). Escribiria alcance `root` donde la
verdad es `host`. Medido el catalogo: 5187 knobs + 1497 privados, 12 valores
llevan hoy `var( --` y ninguno contiene `palette-`.
- `theming-census.ts:2244` `valenced` y `:2246` el rol neutro — el segundo mata
el clasificador `state-layer` entero: sin match, el knob nunca se encola.
Su entrada es `base.ts`, que NADIE normaliza, y el lector de PRODUCCION de
esos mismos valores ya es tolerante (`render-css.ts` VAR_REF_REGEX).
- `theming-sentinel.ts:867` `kindOfTerm` y `:884` los operandos de
`calc/min/max/clamp` — el segundo es el caso que el propio comentario de
arriba documenta (22 longitudes leidas como numeros). Probado por
EQUIVALENCIA sobre las 4566 entradas de base.ts: 0 diferencias de kind ni de
sentinel entre HEAD y la version tolerante.
- `component-audit.ts:957 / :1137 / :1329` (R-2.6, R-4.3, R-4.6) — la 4.3 era
la peor del lote: opera sobre un valor SIN normalizar, tomado directo del
fuente, y su error se INVIERTE (falso positivo: acusa de «capa de estado
artesanal» a una declaracion que lee la del sistema).
- `__statelayer-analysis.ts:28` — NO TOCADA y declarada: su lector es POR
LINEA, asi que un corte trunca el valor ANTES de que la aguja corra. Anadir
tolerancia daria falsa sensacion de cobertura mientras la limitacion real
sigue en pie. (De paso, defecto preexistente NO tocado en :32: un
`new RegExp` con comillas DOBLES hace que su `\s` sea solo `s` y esa regex no
case nunca.)
`\s*` va a AMBOS lados donde la aguja cierra parentesis, porque el corte produce
`var( --x )`; solo a la izquierda donde no lo cierra. La tolerancia esta COPIADA
del hermano de la casa (`recipe-css-contract.test.ts:150`), no inventada.
DOS CORRECCIONES DOCUMENTALES que el pase adversarial caza:
- `component-audit.ts` ≈:1154 justificaba no usar `/g` con un mecanismo FALSO
(«lastIndex viaja entre llamadas»). Medido: un literal inline se re-crea en
cada evaluacion (`true,true,true`) y sobrevive a la bandera POR ACCIDENTE de
donde esta escrito; el arrastre es del OBJETO regex izado a const de modulo
(`true,FALSE,true`). La decision de no usar `/g` sigue siendo buena y ahora
se sostiene en su razon verdadera. El acta del censo queda como esta: sus
agujas SI estan izadas y alli el arrastre es real.
- `theming-census-debt.ts:58-59` se contradecia con su propia linea :96 tras
la reescritura de la caveat: `87 literal` -> `86` y `583 of the 1154 keys` ->
`582 of the 1150`. Verificado contando el ledger (palabras 272+86+1=359 ·
chronos 198+11+14=223 · 582 de 1150) y con `--debt` («1150 registered, 0 new,
0 stale»). La aritmetica cierra con las actas del propio fichero:
1154 −2 (`8ac23047f`) = 1152 −2 (`830cd7671`) = 1150.
CAVEAT DEL LEDGER reescrita: las claves `--palabras-*` ya NO estan «EN
CUESTION» — la clasificacion esta ADJUDICADA (canal de valor del scheme del
documento) y lo que queda pendiente de firma es la EJECUCION, no el veredicto.
Se anade el dato que faltaba y que cambia como se lee el numero: de los 117
nombres consumidos, el esquema por defecto emite 74 y **43 no los emite nunca**,
de modo que para esos el fallback del CSS ES el valor de diseno. (La particion
que yo habia dado, 75/42, era la resta equivocada: suponia que lo emitido es
subconjunto de lo consumido, y `--palabras-scheme-mark-highlight-bg` se emite y
ninguna receta lo lee.)
FICHAS NO REGENERADAS a proposito: `--only` se parsea pero `writeReport()` lo
ignora, asi que regenerar reescribiria las 171 fichas + README — 172 ficheros
mezclando todos los carriles. Las de split-button y palabras quedan desfasadas
hasta que alguien corra `--report` sobre un arbol quieto.
VERIFICADO: censo antes/despues byte a byte IDENTICO · `component:audit`
PASS 161 / NEEDS-WORK 5 / BROKEN 0, con `tmp/component-audit.md` byte a byte
salvo la linea `Date:` autogenerada · `theming-reach-floor` 5/5 verde · ningun
fichero bajo `web/` tocado (regla del autor: ese arbol se reconstruye).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
if ( ! /var\(\s*--color-(surface|content|border|neutral)\s*[-)]/ . test ( value ) ) return false ;
fix(eidos): la capa de estado es un VELO — 38 hovers que no lo eran, al idioma
El codemod de ayer apartó 47 knobs «en cola de migración a la capa de estado».
Al empezar esa migración se ve que sólo SEIS lo estaban: el velo de §38 es
`background-image: linear-gradient(var(--state-hover), var(--state-hover))` y
no alcanza nada más. De los otros 41, veintidós mueven un BORDE y diecinueve
mueven la TINTA — ninguno es la capa de estado por muy neutro que sea su valor.
Eran deuda de nombre, y yo los había excluido del renombrado.
El clasificador tenía media prueba: miraba el VALOR (¿neutro o con valencia?) y
no la PROPIEDAD. Ahora exige las dos, siguiendo un salto por privado
(`--_switch-track-bg-hover`) y entre componentes (la familia calendar presta
`--calendar-control-*` a month-grid, range-calendar y year-grid, y por eso
esos cuatro no aparecían consumidos en su propia receta).
renombres 38 en 22 componentes · 113 referencias en 30 ficheros
cola real 6 (checkbox · date-range-picker ×2 · scroll-area · splitter ·
switch) — los únicos que pintan fondo
censo 162 · 5.203 · 1.841 (37 %) · 56 — IDÉNTICO
--names 38 → 0 desviadas
diff generated 38 renombres 1:1 · 0 cambios de valor
computed field · tabs contra la línea base ORIGINAL (anterior a las dos
pasadas): 4.031 valores, 16 estados, 0 diffs
huérfanos 0 · formato sin regresión · audit 163 PASS · rtl 0 · docs 0 ·
suite 434 pasan (1 rojo conocido) · check 0 errores en ficheros tocados
La muta-prueba se reescribió contra el catálogo vivo — asertaba nombres que el
codemod ya había renombrado — y gana los casos que faltaban: un knob de borde
neutro es `modifier`, uno de tinta neutro es `both`, y sólo los de fondo son
`state-layer`.
Queda una pregunta que §38 no contesta y que NO he decidido: si un hover de
borde o de tinta POR COMPONENTE debe existir siquiera. R-4.3 guarda
`background*` en `:hover`, nada más. Ahora al menos se llaman como deben
mientras se decide.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
return propertiesFed ( ` -- ${ component } - ${ key } ` ) . some ( ( p ) = > /^background/ . test ( p ) ) ;
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
}
/** Classify one recipe key against the signed grammar. */
function classifyName ( component : string , key : string , modifiers : Set < string > ) : NameFinding {
const at = ( klass : NameClass , to? : string , why? : string ) : NameFinding = > ( {
component ,
key ,
klass ,
to ,
why
} ) ;
const override = NAME_OVERRIDE [ component + '.' + key ] ;
if ( override ) return at ( /(^|-)color(-|$)/ . test ( key ) ? 'ink' : 'modifier' , override , 'acta' ) ;
const exempt = NAME_EXEMPT . find ( ( e ) = > e . re . test ( key ) ) ;
if ( exempt ) return at ( 'exempt' , undefined , exempt . why ) ;
const roleSlot =
ROLES . some ( ( r ) = > new RegExp ( '(^|-)' + r + '-(' + ROLE_SLOT_TAILS . join ( '|' ) + ')$' ) . test ( key ) ) ||
VARIANT_TAILS . test ( key ) ;
if ( roleSlot ) return at ( 'role-slot' ) ;
if ( isNeutralHover ( component , key ) ) return at ( 'state-layer' , undefined , 'migra a --state-hover' ) ;
// Peel the trailing INTERACTIVE state only. A contextual qualifier keeps its
// place (see the header): `modifiers` validates it, it never promotes it.
const valid = new Set ( UNIVERSAL_MODIFIERS ) ;
const mods : string [ ] = [ ] ;
let base = key ;
for ( ; ; ) {
const hit = [ . . . valid ] . find ( ( m ) = > base . endsWith ( '-' + m ) && base . length > m . length + 1 ) ;
if ( ! hit ) break ;
mods . unshift ( hit ) ;
base = base . slice ( 0 , - ( hit . length + 1 ) ) ;
}
const ink = /(^|-)color(-|$)/ . test ( base ) && ! /^color-scheme/ . test ( base ) ;
if ( ! ink && mods . length === 0 ) return at ( 'ok' ) ;
// `color` as the ink slot → `fg`; `color` as a medial segment keeps its
// neighbours (`status-color` → `status-fg`).
if ( ink ) base = base === 'color' ? 'fg' : base . replace ( /(^|-)color(-|$)/ , ( _ , a , b ) = > a + 'fg' + b ) ;
const to = mods . length ? mods . join ( '-' ) + '-' + base : base ;
if ( to === key ) return at ( 'ok' ) ;
return at ( ink && mods . length ? 'both' : ink ? 'ink' : 'modifier' , to ) ;
}
export async function names ( only? : string ) : Promise < NameFinding [ ] > {
const out : NameFinding [ ] = [ ] ;
for ( const dir of readdirSync ( ROOT ) ) {
if ( only && dir !== only ) continue ;
if ( recipeBlock ( dir ) === null ) continue ;
const modifiers = await morfoModifiers ( dir ) ;
for ( const key of contractKeyNames ( dir ) ) {
if ( key . startsWith ( '_' ) ) continue ;
out . push ( classifyName ( dir , key , modifiers ) ) ;
}
}
return out ;
}
async function reportNames ( only? : string , asJson = false ) {
const findings = await names ( only ) ;
if ( asJson ) {
process . stdout . write ( JSON . stringify ( findings , null , '\t' ) + '\n' ) ;
return ;
}
const of = ( k : NameClass ) = > findings . filter ( ( f ) = > f . klass === k ) ;
const deviated = [ . . . of ( 'ink' ) , . . . of ( 'modifier' ) , . . . of ( 'both' ) ] ;
const comps = new Set ( deviated . map ( ( f ) = > f . component ) ) ;
console . log ( ` theming-census --names — ${ findings . length } claves públicas ` ) ;
console . log (
` DESVIADAS ${ deviated . length } en ${ comps . size } componentes · ink ${ of ( 'ink' ) . length } · modificador ${ of ( 'modifier' ) . length } · ambas ${ of ( 'both' ) . length } `
) ;
console . log (
` conformes ${ of ( 'ok' ) . length } · role-slot canónicas ${ of ( 'role-slot' ) . length } · exentas ${ of ( 'exempt' ) . length } · capa de estado ${ of ( 'state-layer' ) . length } `
) ;
const per = new Map < string , number > ( ) ;
for ( const f of deviated ) per . set ( f . component , ( per . get ( f . component ) ? ? 0 ) + 1 ) ;
console . log (
' peores: ' +
[ . . . per . entries ( ) ]
. sort ( ( a , b ) = > b [ 1 ] - a [ 1 ] )
. slice ( 0 , 10 )
. map ( ( [ c , n ] ) = > ` ${ c } : ${ n } ` )
. join ( ' ' )
) ;
console . log ( '\n— DESVIADAS (el codemod las renombra) —' ) ;
for ( const f of deviated ) console . log ( ` ${ f . component } . ${ f . key } → ${ f . to } ` ) ;
console . log ( '\n— CAPA DE ESTADO (migran por la firma 3, NO se renombran) —' ) ;
for ( const f of of ( 'state-layer' ) ) console . log ( ` ${ f . component } . ${ f . key } ` ) ;
console . log ( '\n— EXENTAS (firmadas) —' ) ;
for ( const f of of ( 'exempt' ) ) console . log ( ` ${ f . component } . ${ f . key } ( ${ f . why } ) ` ) ;
}
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
function main() {
const args = process . argv . slice ( 2 ) ;
const only = args . includes ( '--only' ) ? args [ args . indexOf ( '--only' ) + 1 ] : undefined ;
refactor(eidos): el catálogo habla un idioma — 269 claves al vocabulario firmado
D-TH.6, ejecutada. El slot de tinta pasa a `fg` y el modificador interactivo
se pone delante, que es lo que theming §6.7 r7 documentaba sin guard desde
que se escribió. Value-preserving: renombra la clave en `recipes/base.ts` y
sus 793 referencias en 101 ficheros; no toca un solo valor.
claves renombradas 269 en 66 componentes
referencias 793 en 101 ficheros
censo antes/después 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56
sin contrato — IDÉNTICO, como debe ser un rename
--names antes/después 269 desviadas → 0
Qué NO entra, y por qué:
47 `{rol}-{slot-de-rol}` canónicas: COLOR_ROLE_SLOTS pone el modificador
detrás POR CONSTRUCCIÓN (`primary-solid-hover`)
13 exentas firmadas `--focus-ring-*` es familia del sistema; el color
de `aura` es un sustantivo; `stop-color` ES una
parte de gradient-builder
47 hovers neutros por VALOR: migran a la capa de estado
(§38 + R-4.3), no se renombran — firma 3
6 ocurrencias en historia changelog, errores-toxico, PLAN-affix/background:
reescribir un registro fechado lo vuelve mentira
El clasificador vive en el censo (`--names`), no en un script suelto, para que
el guard R-5.3 consuma la MISMA gramática que el codemod. Su muta-prueba
(`__names-mutatest.ts`, 27 casos) es lo que hizo el trabajo: cazó que yo
promovía al frente CUALQUIER valor declarado por el morfo, y así
`--sidebar-width-icon` (la anchura del raíl colapsado) se convertía en
`--sidebar-icon-width` (la anchura de un icono), que es otra cosa. La firma
dice «delante lo interactivo, detrás lo dimensional y contextual»: ahora sólo
promociona el vocabulario interactivo cerrado, y lo contextual —`below`,
`loaded`, `vertical`, `icon`— se queda donde estaba. Los cinco casos de
regresión están en la muta-prueba.
También cazó que `dropdown-menu.item-bg-hover` lee `var(--color-primary-element)`:
es un hover CON VALENCIA (el palette swap de recipe-contract §2), no el hover
bespoke que §38 deprecó. Clasificar por el nombre lo habría metido en una
migración que no le toca; se clasifica por el VALOR.
Verificación (§7.4, artefacto por paso):
diff de generated/ 269 renombres 1:1 · 0 cambios de valor · 22 privados
reapuntados a los nombres nuevos (`__names-verify-diff`)
computed tag-group · field · tabs: 6.467 valores en 23 estados,
0 diffs — y comprobado en el navegador que sirve el CSS
nuevo, para que ese 0 no sea el de una copia cacheada
huérfanos 0 en código
suite eidos 434 pasan · 1 rojo, el conocido (`skin-media-player`).
`recipe-css-contract` verde: es el guard que caza el
`var()` sin fallback a un nombre que ya no se emite,
o sea el fallo exacto de un rename a medias
component:audit 163 PASS · 3 NEEDS-WORK, los tres SIN TOCAR por esto
check 0 errores en ficheros tocados (los 73 globales son de
otras sesiones de la rama; se atribuye por fichero)
rtl:check 0 · docs:check 0
formato el renombrado no alarga ninguna línea: ningún fichero
tiene más líneas de 100 chars que antes, así que no se
pasa prettier — hacerlo reformateaba 300 ficheros de
deriva ajena
Entra aquí la corrección del repaso de los 7 ya hechos:
`gradient-builder.checker-color` → `checker-fg` (el damero de transparencia;
ahí `color` era slot). Sus `stop-color-*` no se tocan.
Queda para el paso siguiente: R-5.3 no puede graduar a `error` directo
mientras los 47 hovers neutros sigan hablando el idioma viejo — o migran
antes (firma 3, ya firmada), o el guard necesita una exención greppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( args . includes ( '--names' ) ) {
void reportNames ( only , args . includes ( '--json' ) ) ;
return ;
}
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
if ( args . includes ( '--debt' ) ) {
if ( args . includes ( '--write' ) ) void writeDebtBaseline ( ) ;
else reportDebt ( only , args . includes ( '--json' ) ) ;
return ;
}
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
if ( args . includes ( '--residue' ) ) {
const next = args [ args . indexOf ( '--residue' ) + 1 ] ;
reportResidue ( only , next && ! next . startsWith ( '--' ) ? next : undefined , args . includes ( '--json' ) ) ;
return ;
}
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( args . includes ( '--report' ) ) {
writeReport ( ) ;
return ;
}
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
const { rows , debt } = censusAudit ( only ) ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if ( args . includes ( '--json' ) ) {
process . stdout . write ( JSON . stringify ( rows , null , '\t' ) + '\n' ) ;
return ;
}
const sum = ( k : keyof CensusRow ) = > rows . reduce ( ( s , r ) = > s + ( r [ k ] as number ) , 0 ) ;
const themeable = sum ( 'public' ) + sum ( 'private' ) + sum ( 'global' ) + sum ( 'literal' ) ;
console . log ( ` theming-census — ${ rows . length } component recipe(s) ` ) ;
console . log (
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
` appearance knobs ${ sum ( 'knobs' ) } · public ${ sum ( 'public' ) } ( ${ pct ( sum ( 'public' ) , themeable ) } reach) · private ${ sum ( 'private' ) } · global ${ sum ( 'global' ) } · literal ${ sum ( 'literal' ) } · system ${ sum ( 'system' ) } · exception ${ sum ( 'exception' ) } · structural ${ sum ( 'structural' ) } · bridge ${ sum ( 'bridge' ) } · channel ${ sum ( 'channel' ) } `
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
) ;
console . log (
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
` no contract entry: ${ rows . filter ( ( r ) = > r . contractKeys === 0 && ! STRUCTURAL_COMPONENTS . has ( r . component ) ) . length } · reach < 20%: ${ rows . filter ( ( r ) = > r . reach < 0.2 ) . length } · reach = 100%: ${ rows . filter ( ( r ) = > r . reach === 1 && r . public > 0 ) . length } · with data-size: ${ rows . filter ( ( r ) = > r . hasSize ) . length } · structural: ${ rows . filter ( ( r ) = > STRUCTURAL_COMPONENTS . has ( r . component ) ) . length } `
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
) ;
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave
La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no
se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave,
y desde ahi la regresion es imposible y la mejora queda contada.
- scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325
literal, 74 componentes), clave `{clase} - {fichero} - {selector} -
{propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica
regresion), comparacion MULTISET, generacion reproducible (dos corridas =
mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es
deuda registrada, la otra clase de acta - la valvula R-5.x exception de
los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN
(palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y
dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol.
Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son
canal de VALOR del scheme del documento, no contrato de tema - sus 103
"public" del censo estan en cuestion.
- theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt
[--write] que imprime el delta que va a cometer (regenerar en masa borra
el ratchet: el escritor grita y la cabecera lo prohibe sin firma).
- theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las
claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS
(superseded por el por-clave: 5 regresiones ya no se esconden bajo 5
arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco
nombrado: los 318 privados no-derivados siguen SIN ratchet por clave
(acotado por la firma a literal|global; pendiente de firma propia);
atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia
eran denominadores vacios, ninguno un avance real).
- component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit()
(dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre
los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs
cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa
calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback)
sin contrato - forma real sin nombre, pendiente de decision; palabras
fuera del catalogo del audit). R-5.3 YA estaba en error (verificado,
--names 0 desviadas). El skip por censo roto ahora GRITA por consola (la
leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de
vitest queda de red mecanica).
- docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la
ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos
filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza
su linea de enforcement.
Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra,
suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar
la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en
HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x
ajenos), suelo 5/5, docs:check 0/0.
BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un
literal o un global crudo a una receta exige desde ahora tokenizar, anotar
/* literal: */ o firmar la entrada en el ledger de deuda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// The ratchet, in the same breath as the totals it used to hide behind:
// five regressions under five fixes moved neither `literal` nor `global`.
console . log (
` debt ledger: ${ debt . registered } registered · ${ debt . newDebt . length } NEW · ${ debt . stale . length } STALE (detail: --debt) `
) ;
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
console . log ( '' ) ;
const sorted = [ . . . rows ] . sort ( ( a , b ) = > a . reach - b . reach || b . knobs - a . knobs ) ;
console . log (
'component' . padEnd ( 24 ) +
'reach' . padStart ( 6 ) +
'knobs' . padStart ( 7 ) +
'public' . padStart ( 8 ) +
'private' . padStart ( 9 ) +
'global' . padStart ( 8 ) +
'literal' . padStart ( 9 ) +
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception`
Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda
resultan ser desviaciones que el canon ya había firmado.
`recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */`
EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre
(`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el
único instrumento que no: su `strip()` blanquea los comentarios ANTES de
clasificar, así que las **81 anotaciones del catálogo** contaban como literales
sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la
del que no sabía leerlo.
Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como
`system`, y por la misma razón: no es que un tema no lo alcance, es que el canon
ya aceptó por escrito que ahí no va un token.
**54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que
su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human`
16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**),
`result` 1 (86 % → **90 %**), y siete más con una cada uno.
Detalles que costaron medición:
- **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final. Se comprueban
todas las líneas que abarca la declaración.
- **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los
saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea
y basta con guardar el crudo al lado.
- Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a
`literal` (21 → 20) y el conteo vuelve al restaurarla.
Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que
las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna
parte; ahora la ficha del componente las enseña, que es donde se decide si una
sigue siendo cierta.
Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de
reparto); el README de la auditoría gana la fila de la clase y la nota de que
`system` y `exception` quedan fuera del denominador.
Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check
0 · `check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'excep' . padStart ( 7 ) +
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'system' . padStart ( 8 ) +
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
'strct' . padStart ( 7 ) +
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
'bridge' . padStart ( 8 ) +
'chan' . padStart ( 6 ) +
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'contract' . padStart ( 10 ) +
' size'
) ;
for ( const r of sorted ) {
console . log (
r . component . padEnd ( 24 ) +
docs(theming): la auditoría de alcance de tema, componente a componente, con su propuesta
El censo medía el eje y no lo explicaba: 5.205 knobs en dos tablas del plan no
dicen QUÉ knob de QUÉ línea no alcanza un tema, ni qué token habría que crear.
Ahora `theming-census.ts --report` escribe la auditoría entera bajo
`docs/audit/theming/`: un README con la vista de conjunto y 170 fichas — 162
recetas con CSS + 8 componentes sin receta, el árbol completo de
`eidos/components/`.
Cada ficha de receta: knobs fuera de alcance con fichero:línea Y selector
(agrupados por clase), sistema transversal aparte, los privados con la columna
que decide («¿deriva de un público?»), y una PROPUESTA de corrección — el token
a declarar en `base.ts` con su valor VERBATIM y su scope TSC. La propuesta
deriva los nombres de recipe-contract §1 y theming §6.7; donde la doctrina no
decide, marca `⚠ decisión` en vez de inventar. Y no propone acuñar lo que la
doctrina prohíbe: lo que posee una capa compartida, el foco, la capa de estado,
un token prestado de otra receta, un shorthand o un eje físico.
Las 8 fichas sin receta dicen —medido, no supuesto— dónde vive su visual: el
componente que componen (icon-button → button), la capa que consumen (affix →
viewport-placement) o la receta ajena que pinta sus attrs (svg → badge/button).
Defectos del instrumento corregidos en el mismo pase, todos encontrados
mirando la salida: un `@import` pegado al primer selector se comía 2 knobs de
color-picker (el suelo del censo no puede moverse); `:not(:disabled)` se leía
como estado y producía `hover-disabled-*`; un knob que lee un privado se
proponía a sí mismo en vez de resolverse por talla; `--icon-size-sm` se
reportaba como préstamo del componente `icon` (ahora el préstamo se verifica
contra las claves del dueño en `base.ts`); y el default de talla salía sin
nombrar en vez de `-md`.
Verificación: censo global idéntico al suelo publicado (5205 · 1621/33% · 856 ·
1880 · 614 · 234) y COMPONENTE A COMPONENTE contra la tabla §9 del plan — 0
diferencias en 162 · regenerar dos veces da el árbol idéntico · el bloque de
veredicto escrito a mano sobrevive · docs:check 0 errores sobre 813 docs ·
prettier limpio (el árbol generado entra en .prettierignore junto a
eidos/generated).
Las 10 primeras fichas llevan ya su veredicto de revisión: análisis correcto en
las 10, propuesta apta en 2 (gradient-builder como piloto, combobox con cinco
correcciones — dos de ellas evitaban romper el default), y 5 que no se
tokenizan en solitario porque son alias de --calendar-*/--field-* y esperan la
decisión de familia. Cuatro destaparon privados con prefijo ajeno o abreviado
declarados en su propio CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
reachOf ( r ) . padStart ( 6 ) +
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
String ( r . knobs ) . padStart ( 7 ) +
String ( r . public ) . padStart ( 8 ) +
String ( r . private ) . padStart ( 9 ) +
String ( r . global ) . padStart ( 8 ) +
String ( r . literal ) . padStart ( 9 ) +
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception`
Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda
resultan ser desviaciones que el canon ya había firmado.
`recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */`
EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre
(`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el
único instrumento que no: su `strip()` blanquea los comentarios ANTES de
clasificar, así que las **81 anotaciones del catálogo** contaban como literales
sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la
del que no sabía leerlo.
Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como
`system`, y por la misma razón: no es que un tema no lo alcance, es que el canon
ya aceptó por escrito que ahí no va un token.
**54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que
su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human`
16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**),
`result` 1 (86 % → **90 %**), y siete más con una cada uno.
Detalles que costaron medición:
- **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final. Se comprueban
todas las líneas que abarca la declaración.
- **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los
saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea
y basta con guardar el crudo al lado.
- Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a
`literal` (21 → 20) y el conteo vuelve al restaurarla.
Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que
las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna
parte; ahora la ficha del componente las enseña, que es donde se decide si una
sigue siendo cierta.
Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de
reparto); el README de la auditoría gana la fila de la clase y la nota de que
`system` y `exception` quedan fuera del denominador.
Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check
0 · `check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
String ( r . exception ) . padStart ( 7 ) +
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
String ( r . system ) . padStart ( 8 ) +
feat(theming): la clase structural del censo - el 0% por NATURALEZA deja de contar como deuda
Pieza del CIERRE del eje (SS13: el censo no distingue "0% por deuda" de "0%
POR NATURALEZA" y el gate de F3 era inalcanzable por construccion). Nueva
clase structural en theming-census.ts, la forma de LAYER_VOCABULARY: la
lista Y la razon por componente EN el artefacto, por COMPONENTE entero, con
el SS5 firmado de cada ficha como fuente. No es un cajon para "este es
dificil": una entrada exige veredicto escrito, y un componente que gane
superficie de tema real sale de la lista.
Los cinco medidos 2026-08-22: aspect-ratio (faceta de box, knob prestado) -
text-blur (el 1px de la tecnica sr-only) - cascade y motion (el opacity del
gate antiparpadeo, mecanica del canal cuyo valor vive en EidosConfig.motion)
- date-picker (la correccion max-content del pie, un unico valor correcto).
Aritmetica cuadrada: structural 9 knobs (global -1, literal -8), knobs 4998
intacto (salen del DENOMINADOR, como system), reach 68% -> 69%, no-contract
23 -> 18, <20% 12 -> 7. Diff de tablas: exactamente 5 filas cambian
(0% -> strct), las otras 157 byte a byte. Los knobs estructurales se siguen
LISTANDO en SS2-bis de su ficha con su razon; la seccion de propuestas los
excluye (proponer un token contradiria el SS5).
component:audit identico (162 PASS; el NEEDS-WORK de motion es R-1.1, raiz
sin declarar, ortogonal al alcance). Suelo del censo verde con margen.
Candidatos NO incluidos, reportados con dato: field-langs es deuda REAL (31
globales crudos) - range-calendar/month-grid/year-grid son la pregunta
abierta de las capas compartidas - display/heading/text ya son all-system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
String ( r . structural ) . padStart ( 7 ) +
feat(theming): el censo distingue PUENTE y CANAL - 318 privados dejan de ser un bulto
Primera pieza de la firma "clasificacion de los 318" (F1). Dos clases nuevas
de knob, MEDIDAS contra el emisor y no adivinadas por nombre (la ceguera del
chooser, escarmentada en este mismo eje):
- bridge (132): el knob lee el privado que el forward THM-2 ESCRIBE -
verificado contra la CSS generada real (la linea `--_{c}-palette-{slot}:
var(--palette-{slot}, ...)` de renderRecipePaletteForward), no contra la
forma del nombre: un `--_c-palette-shadow` que nadie forwardea se queda en
residuo. Alcanzable DOS veces (capa --palette-* + tonos publicos); un
publico encima dejaria a un tema PINearlo y matar el color= por instancia
(los SS5 de card/tags-input/avatar ya lo decian).
- channel (36): el privado que NADIE declara - ni la receta ni el generador -
porque soma o el envoltorio lo escriben POR INSTANCIA (29 nombres, cada
uno con su escritor real verificado). Un tema no debe alcanzarlo.
- Precedencia puente > canal > residuo; `every`, no `some`; un privado
GENERADO nunca es canal (los 4 knobs de gradient finish quedan en residuo
con su razon medida - adjudicar una clase "finish" seria inventarla sin
firma).
Aritmetica: 318 = 132 puente + 36 canal + 150 RESIDUO (la adjudicacion por
clave es de F2). Ambas clases salen del denominador como system/structural:
reach 69% -> 71% (3074/4312). Cero knobs de otras clases movidos (diff
programatico de las 162 filas). Mutacion del instrumento sobre copia:
vaciar los forwards -> bridge 0; declarar-todo -> channel 0 - las clases
las manda la medida. Nuevo `--residue <out>` vuelca el residuo con la
cadena de fuentes de cada privado (el material de F2).
Casos firmados, contrastados: card 8 = 4 puente + 4 residuo (su SS5 exacto) -
tags-input 4/4 puente - toolbar 2 residuo ("esta barra no tiene paleta") -
avatar 6 residuo (su canal custom esta un nivel POR DEBAJO del knob) - el
indicador de tabs NO esta en los 318 (es clase global y ya vive en el
ledger de deuda).
Suelo 5/5 verde (el ledger literal|global no se toca; el ratchet grueso
sube en F3), audit 162 PASS identico, tsc 0 propios, prettier sin
desajustes nuevos. Deuda de prosa (los "318" en cabeceras/docs y el suelo
en 69) queda para F3, nombrada.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
String ( r . bridge ) . padStart ( 8 ) +
String ( r . channel ) . padStart ( 6 ) +
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente
El autor, al ver que el radio del trigger del nav vivía en un privado clavado a
--radius-default: «¿los componentes son themables? si no lo son, es un error
como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs
en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo
comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en
var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar
moviendo el sistema entero.
Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205
knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del
componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son
literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen
alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %.
El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla
regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens ·
talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para
component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el
autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar
nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error ·
panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el
PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla
mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda
headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la
forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada
token nuevo, desde el píxel hacia arriba, registro de animationstart/end),
guards por fichero, un componente = un commit con sus artefactos, y revisión
adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho.
El instrumento se probó por mutación antes de creerle, y falló la primera vez:
no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que
meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos
mutaciones. Las cifras del plan son las del instrumento final, y el handoff
(CONTINUE-theming.md) obliga a reproducirlas antes de seguir.
Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del
script limpio · tsc del script sin errores · el plan cita ficheros que existen
(los once, comprobados).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
String ( r . contractKeys ) . padStart ( 10 ) +
( r . hasSize ? ' y' : ' -' )
) ;
}
}
if ( process . argv [ 1 ] && /theming-census\.ts$/ . test ( process . argv [ 1 ] . replace ( /\\/g , '/' ) ) ) main ( ) ;