La decisión 7 abre la entrada sin `value`. Las tablas de contrato de las
demos desreferencian `{a.value.kind}` PELADO y el gate de tipos está CIEGO
por construcción (las páginas castean la parte cruda con `as unknown as`),
así que `npm run check` no lo cazaría jamás: la PRIMERA entrada sin value
reventaría esa tabla con un TypeError en runtime.
Endurecidas al patrón que la casa ya tenía en dos sitios (el gemelo de data[]
diez líneas más arriba en la propia página y date-range-field:874):
`{a.value?.kind ?? '—'}`. El guion solo aparece cuando no hay fuente; con
value, las tablas pintan exactamente lo mismo que antes.
Hoy hay CERO entradas sin value en el corpus, así que esto no cura un defecto
vivo: desarma una bomba ARMADA. Hallazgo del peer vicen-e1 en su medida de la
decisión 7.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El panel de Tokens existe precisamente para no escribir a mano lo que el
contrato ya sabe: pregunta a ActiveEidos.getRecipeTokens(). Pero el número
junto a la etiqueta de la pestaña SÍ estaba escrito a mano, y derivó como
deriva siempre una segunda fuente de verdad.
Auditadas las 105 demos que montan el panel: 90 contadores correctos, 15 sin
contador y TRES que mentían — carousel decía 30 y son 27, drag-drop 10 y son 9,
link-preview 33 y son 35. Nadie edita un badge cuando cambia una receta.
Nace web/routes/uix/lib/TokensTabCount.svelte, que hace la MISMA pregunta que
el panel de debajo y filtra los forwards con guion bajo. 102 demos convertidas;
las tres que faltan (scroll-area, slider, tabs) están en manos de otra sesión y
van en una segunda pasada, para que el índice compartido no vea nunca dos
escritores sobre un fichero.
Verificado en navegador: carousel pasa de decir 30 a decir 27, con 27 filas en
el panel y ninguna sin resolver; metrics, que no tenía contador, dice 72 y lista
72. npm run check COMPLETED con los 72 errores preexistentes de la rama y
ninguno en los 102 ficheros.
Dos sesiones de esta tanda discreparon sobre si poner el contador —una lo puso
a mano, otra lo omitió por ser justo lo que el panel evita—. Tenían razón las
dos a medias: el contador es útil y no debe escribirse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Una clave nueva (51 → 52) y veinticinco adjudicaciones medidas: trece propias y
doce por patrón. El default no se mueve: diff de computed 0 sobre 1.344 valores
en 36 estados y capturas 2x idénticas.
Censo: 76 % → 81 % (público 16 → 17, global 1 → 0; 22 knobs, 4 privados y 1
sistema fuera). Global 67 % (no baja).
QUÉ ENTRA (1 clave)
invalid-track-border (var(--color-threat-element)). Era el ÚNICO knob de
apariencia que iba a un primitivo global a pelo.
El nombre NO es el `invalid-border` que el catálogo repite seis veces
(checkbox, listbox, grid-list, radio-cards, date-range-field…): esta receta
tiene DOS partes con borde y las nombra — su hermana de estado es
`disabled-track-border` y el pulgar lleva `thumb-border`, así que un nombre
desnudo no diría cuál de las dos tiñe. La forma modificador + parte + ranura ya
está en el catálogo (`invalid-input-border`, `invalid-control-border`).
DOCE CLAVES DE TONO NO PINTAN — Y AQUÍ CAE TAMBIÉN «NEUTRAL»
Es la cascada de paleta ya medida en button / badge / callout: el forward emite
al FINAL un bloque genérico [data-switch][data-color] que resuelve desde el
--palette-* global, misma especificidad (0,2,0), gana el último.
Lo propio de este componente es que el fallback no salva ni al tono por
defecto: soma estampa data-color SIEMPRE (es el color RESUELTO, neutral por
defecto), así que el bloque genérico casa en toda instancia. Medido en checked:
--switch-neutral-solid no mueve nada, --palette-solid sobre el nodo repinta
(oklch(0.5556 …) -> rgb(4,5,6)). En button el neutral sobrevive; aquí no.
Adjudicadas POR PATRÓN, con esa diferencia escrita.
LAS OTRAS TRECE SON ESTADO O INSTRUMENTO
Cinco disabled-* y el invalid-track-border nuevo sólo existen bajo su atributo,
que ningún trigger alcanza (forzados: alcanzan, con su par medido).
Los cinco size-*-thumb-translate son DOS huecos a la vez: pintan sobre
`transform`, que el guard no fotografía, y valen 0px hasta que el interruptor
está encendido (forzado checked: 12/14/16/18/20px -> 1234px).
El par de transición ES la transición que el guard congela (sin congelar:
0,12s -> 11,5s en las cuatro propiedades).
FUERA POR DOCTRINA (los 4 privados que quedan)
--_switch-track-bg y --_switch-track-border-hover son CONMUTADORES: dos fuentes
cada uno (la clave pública del estado apagado, el forward de paleta del
encendido), y aplanarlos obligaría a duplicar cada regla por color.
Las dos lecturas directas de --_switch-palette-solid* (el fondo del track en
hover, la tinta del check dentro del pulgar) son el puente THM-2: un público
encima dejaría que un tema fijara el tono y matara el color= de cada instancia.
El techo honesto de este componente NO es el 100 %.
DEFECTO ANOTADO, NO ARREGLADO
El tinte inválido es --color-threat-element, el paso `element` de la escala:
oklch(0.9555 0.0207 13.86), casi blanco sobre superficie clara. Y en cuanto
entra el puntero, la regla de hover (0,4,0) lo tapa con el borde neutro fuerte.
Es el MISMO valor en checkbox, radio-group y toggle para el mismo estado, así
que es una decisión de escala del sistema (element contra border), no de esta
receta: cambiarlo mueve píxel en cuatro componentes y D-TH.5 lo deja fuera.
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (416 valores · 7 estados) + sonda de
estados propia 0 diffs (928 valores · 29 estados: checked, invalid, readonly,
disabled, RTL y las cinco tallas, cada uno con hover y foco) · capturas 2x
antes/después idénticas · R-5.4 27/52, exit 0, sin STALE · censo --only 81 % ·
component:audit PASS · eidos-lint 13 morfo-backed / 0 invalid · vitest
src/uix/eidos 35/36 (el rojo conocido es skin-media-player) · rtl:check 0 ·
docs:check 0 · npm run check sin errores en ficheros del componente · pestaña
Tokens verificada en el navegador (52 filas, 0 errores de consola).
Índice construido a mano desde HEAD sobre los cuatro ficheros compartidos
(recipes/base.ts, generated/base.css, theming-sentinel-exceptions.ts): hay tres
sesiones más en vuelo sobre ellos. Fuera del commit, como en el de `form`,
docs/audit/theming/README.md — es un agregado global que mezcla ese trabajo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El fix anterior (`value={customColor || '#000000'}`) NO servía: el warning
"specified value '' does not conform to #rrggbb" NO viene del valor, sino de que
Svelte pone `""` en el input MOMENTÁNEAMENTE durante la HIDRATACIÓN cuando hay un
`value=`/`bind:value` reactivo (el navegador lo rechaza y resetea el input a
#000000). Diagnóstico ya documentado en `web/routes/temas/color/+page.svelte`.
Fix correcto: action `colorValue` (nueva en `lib/harness.svelte.ts`) que setea el
valor imperativamente (`node.value = hex || '#000000'`), sin atributo `value`
gestionado por Svelte → no hay `""` en hidratación. Los 21 demos con picker custom
pasan de `value={...}` a `use:colorValue={customColor}`.
Verificado tras reiniciar el dev server (el HMR de Vite servía código viejo): el
input conserva #8e4ec6, sin reset a #000000, sin warning. check: 76 (0 en tocados).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El input de color custom de los demos usaba `value={customColor}` y `customColor`
arranca vacío (''), que NO es un valor válido para `<input type="color">` (exige
`#rrggbb`) → warning en consola: "The specified value "" does not conform to the
required format".
Fix uniforme en los 21 demos con picker custom: `value={customColor || '#000000'}`.
PalettePicker.svelte ya estaba bien (input sin `value`, no controlado). Verificado
en el demo de toggle: el warning desaparece.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Motor (Fase 0): tipo abierto `ComponentColorProp`, helper `resolveComponentColor`,
derivación custom compartida `[data-color-custom]` (color-mix desde `--color-custom`)
+ forward extendido a `[data-{c}][data-color-custom]`; guard recipe-css-contract
actualizado.
Eidos-wrapper (14): radio-group, checkbox, stepper, toggle-group, select, badge,
editable, file-upload, tag-group, tags-input, surface, avatar, card. Patrón:
tipo -> ComponentColorProp, wrapper -> resolveComponentColor (estampa
data-color/data-color-custom/style), demo con picker + input custom. Incluye el
fix del bug de escalas de card (una escala caía al path custom y pintaba el CSS
named color en vez de la escala de paleta).
Soma-routed (button/switch/toggle): patrón morfo+soma limpio — el provider omite
data-color y emite data-color-custom para el valor crudo, con el intent evaluativo
ganando y suprimiendo el custom. Ensancha los tipos de color de la capa soma.
Arreglados 2 bugs del runtime del framework:
- html-presence con `v.literal('')` (valor vacío = falsy) nunca emitía el attr ->
`v.propRef('colorCustom')` (como data-disabled).
- el custom pisaba al intent evaluativo -> el prop `colorCustom` se anula con
intent no-neutral.
Bundle: incluye el rail segmentado de radio-group (variant=segmented, indicador
deslizante compartido soma-mide/eidos-pinta: measured-indicator + sliding-indicator)
de la misma sesión, intercalado en los ficheros de radio-group.
Handoff para continuar: docs/process/open-color-cage-2026-07.md (Fase 3/4/5 pendientes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>