astra
alpha-0.1-background
alpha-0.1-dir-prefs
alpha-0.1-sec-dom
menubar-v4-safe
active-uix
morfo-runtime
morfo-driven-soma
semantuix
glm-5
main
sium-v1.0
${ noResults }
68 Commits (468a9d1134d697546f1b36231d6773d6324a5faa)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
468a9d1134 |
style: prettier de una sola vez sobre el repositorio (F5 del cierre, parte 3)
Formato puro: `npm run format` (dos pases; siete ficheros no convergían en el primero) sobre todo lo que .prettierignore no excluye (web/ congelado, .claude/, artefactos generados). Este commit va en .git-blame-ignore-revs. Neutralidad medida fichero a fichero, compilando y minificando con esbuild (y con el compilador de Svelte, cliente y servidor, para .svelte) la versión de HEAD y la formateada: 1 031 ficheros de código idénticos; 9 CSS que solo difieren en espacios pegados a un paréntesis (`var( --x )` frente a `var(--x)`), que CSS no tokeniza como significativos; 510 Markdown sin compilación posible (docs:check los valida). Los dos generados y el guard que dependía del formato se resolvieron en el commit anterior. Verificación sobre este árbol: npm run lint exit 0 · gate entero verde salvo focus-census, arreglado en el commit anterior y re-ejecutado (9/9): check:gate OK (src/ y scripts/ a cero) · docs:check 0/0 · arts/blocks/packs/rtl/ translations/agent · eidos:lint 0 inválidos · apps:check (boot 27/27, 0 errores) · suite 465/466 → 466/466 con el guard corregido · build de la raíz exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
3 weeks ago |
|
|
4f3425783b |
docs: el contrato de consumo — una app del workspace consume UIX como fuente (docs/consuming.md)
El framework no tenía ni una línea sobre cómo se consume desde fuera de su propio árbol de demos: todo el corpus asumía acceso al repositorio. El autor decidió que la app que viene después lo consume COMO FUENTE, en un workspace de este repositorio, sin paquete npm ni exports ni bin. docs/consuming.md es ese contrato, sección a sección, enlazando lo que ya tiene página propia en vez de copiarlo: dónde vive la app (apps/<name>, un solo node_modules y una sola copia de Svelte), los alias (resolveUixAliases en kit.alias), el modo runas por config, la raíz de composición standalone (attach existe y no lo consume nadie), el CSS y los assets servidos por URL (fuentes y sonidos se copian al static/ de la app), y el boot pre-hidratación (la receta de la guía de theming con <uix> = ../..). docs/README.md lo enlaza en E4 y en «I want to…». La guía de theming deja de llamar «fila abierta» al empaquetado: el consumo es un contrato de workspace. La app base de F3 es el primer consumidor real y la verificación del contrato: lo que no se sostenga al construirla se corrige aquí. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
3 weeks ago |
|
|
29e581c721 |
feat(active-uix)!: el boot lo compila el build del SITIO con su propio esquema, y lo que UIX escribe en <html dir> lleva marca de propiedad (fila 2 del boot, F1 del cierre)
F1 del plan de cierre del framework. Cierra el último trabajo del eje boot.
EL DEFECTO. El boot por defecto compilaba un catálogo de preferencias de UN
solo idioma (createDefaultUixPrefsSchema), y la raíz sembraba su entorno
leyendo el <html dir> que el boot acababa de escribir: un usuario árabe de
una app multiidioma no veía un parpadeo, se quedaba en LTR TODA la sesión,
porque el runtime tomaba la salida del boot como si la hubiera declarado la
página.
EL COMPILADOR POR SITIO. Una sola fusión, composeUixPrefsSchema, la consumen
createActiveUix y el boot: no puede derivar. El generador acepta --schema y
--out (el especificador boot/boot-schema.ts apunta al esquema del sitio o a
boot/default-schema.ts); los guards (sin runas, techo de tamaño, ASCII, sin
</script, valores string por atributo) son errores del COMPILADOR con mensajes
para el consumidor, porque ahora el artefacto lo produce el sitio. Un esquema
que importa el barrel $prefs se rechaza nombrándolo. scripts/uix-boot-check.ts
es el guard de rancidez: plugin de Vite que tumba el build con un artefacto
viejo (probado con vite build real) y función para CI. Delta cero por esquema:
por defecto, multiidioma con árabe, ejes redefinidos y esquema parcial.
LA MARCA DE PROPIEDAD, diseño firmado por el autor. El canon de dirección dice
que <html dir> es una proyección y nunca una fuente, con una sola excepción: el
dir del AUTOR en la plantilla o el servidor. El boot rompió la premisa de esa
excepción. Todo lo que UIX escribe en <html dir> lleva ahora
PREFS_DIR_PROJECTED_ATTR (el boot con valor «boot», cada proyección del runtime
con un token de instancia); la semilla solo adopta un dir SIN marca. Un dir
escrito por script no es una fuente: en ejecución la dirección se afirma con
prefs.setIntent('direction') o options.prefs.environment, que ganan a la
semilla. Se descartaron, midiendo, la marca con valor (cierra el script y
congela la dirección al navegar entre layouts) y la inferencia por valor.
dispose solo retira lo que todavía es SUYO: SvelteKit crea la raíz del layout
nuevo ANTES de destruir la vieja (medido), y un retiro a ciegas dejaba el
<html> de la raíz nueva sin dir, lang ni data-motion/sound/haptic (medido hoy:
los nueve atributos a null). Mismo principio que el unstamp de sema, que
comprueba data-event-id.
esbuild se declara como devDependency EXACTA 0.27.4: los bytes del boot y su
hash dependen del minificador, y una subida dentro de un rango rompería la
sincronía. Boot 15 198 B, sha256-hsqdGYcrRu3oEc0Q3G/A67ApQT3q9c/vT9zMDgxROg8=
(el hash se mueve: firmado por el autor).
VERIFICACIÓN. Constructor Opus en cuatro rondas y adversarial Opus en dos
pasadas independientes, con sus reproducciones repetidas tras cada cierre:
en Chromium, entrar en árabe y pasar a inglés y a español sigue al idioma, y al
revés también; el dir de plantilla gana antes y después de hidratar y en una
raíz recreada; una raíz recreada sobre una viva sigue al idioma con y sin boot;
tras create b → destroy a, <html> conserva lo que proyectó b. Diez defectos
declarados por el adversarial, cerrados (D1–D10): marca de propiedad, esquema
parcial que estampaba "undefined", vigilante de dev mudo, plugin sin test,
receta de CI que no cargaba en jsdom, cifras y prosa, y un vite build real que
se cae cuando el boot no compila. Mutaciones en rojo, restauradas byte a byte:
la proyección no marca · dispose sin guard de dueño (también repetida por el
coordinador: 3 rojos) · la semilla compara valor en vez de presencia · marcar
el dir del autor. Suite entera 463 ficheros / 5 437 tests, exit 0 · check 0 errores en src/ y en scripts/ (89 en web/, ledger intacto) ·
docs:check 0/0 · generate:boot dos veces byte-idéntico.
LO QUE NO CIERRA (al ledger de cierre): (1) PREEXISTENTE — en la misma
navegación entre layouts, el ActiveEidos.dispose de la raíz vieja sigue
retirando data-theme/mode/density/scaling de la raíz nueva; exige cambiar el
dispose de eidos, otra capa. (2) La propiedad de los atributos proyectados
cuelga de la marca de dir: si la plantilla declara la dirección, dispose deja
lang y data-motion puestos cuando la raíz se desmonta sin sustituta
(benigno). (3) Sin boot, un script que escribe dir antes de la primera raíz es
indistinguible de la plantilla y se adopta. (4) El vigilante de dev no
reacciona a ficheros nuevos ni a inputs fuera del root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
3 weeks ago |
|
|
9ae58c2365 |
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
3 weeks ago |
|
|
418297a82a |
test(active-uix): el boot se prueba por el tag REAL que inyecta el sitio, y el nonce se valida contra la gramática de CSP
`renderUixBootScript` es el único camino de producción al boot pre-hidratación (guía de theming, §59) y ningún test lo importaba. La suite de delta cero no lo llamaba: ejecutaba una COPIA de su envoltorio con `new Function`, la segunda implementación que la doctrina del boot prohíbe («compilado, no escrito»). Nadie vigilaba que `pins` llegase al boot, que el escape de `<` impidiera cerrar el tag antes de tiempo, ni el atributo `nonce`. Tres decisiones firmadas por el autor: 1. `boot/render.test.ts` (nuevo, jsdom): forma del tag (un `<script>`, un solo cierre, ningún `<!--`); nonce ausente, válido (base64 y base64url) e inválido; reenvío de TODOS los parámetros medido por comportamiento en `<html>` (`defaultLocale`, `pins`, `themeIds`, `storageKey`); ida y vuelta byte a byte de una clave y un themeId hostiles con `</script>` y `<!--` sin inyectar markup; ningún global tras ejecutar; higiene del bundle generado. 2. La suite de delta ejecuta el tag real: muere `runGeneratedBoot` con su `new Function` y `BOOT_SOURCE`. El único ejecutor vive en `test/boot-script-tag.ts` (DOMParser inerte + `vm.runInThisContext`, script clásico en ámbito global). No eval indirecto: el bundle abre con `"use strict"` y el eval estricto guarda sus `var` en un entorno propio, así que una fuga de `__uixBoot` habría sido invisible (medido en Node y Chromium; un control positivo lo pone en rojo si vuelve). 3. El nonce se valida contra CSP Level 3 `base64-value` y fuera de ella lanza `ActiveUixInvalidBootNonceError` (`uix::boot.invalid_nonce`, con la longitud, nunca el valor). `JSON.stringify` escapaba para JavaScript y no para un atributo HTML: `a"b` salía como `nonce="a\"` más un atributo basura, y `<` llegaba decodificado. La validación ES el escape. Razón precisa (corregida por el adversarial antes de anclar): fuera de la gramática el valor no es un `nonce-source` válido; Chromium 145 y Firefox 146 no lo casan, WebKit 26 tolera `=` de más, así que ninguna política portable puede apoyarse en él. El nonce de SvelteKit 2.55.0 (`btoa` sobre bytes aleatorios) siempre la cumple. `render.ts` localiza el bundle con una ruta de disco resuelta desde el módulo en vez del idioma `new URL(…, import.meta.url)`, que el entorno jsdom de Vitest reescribe a `self.location`. Medido por el adversarial con un build REAL de Vite 7.3.1: neutro en producción (build SSR suelto y con disposición tipo Kit: las dos formas se comportan igual), funciona en dev SSR, y solo la nueva funciona bajo Vitest jsdom. El barrel exporta la clase de error junto a las otras seis. Verificación: suite de `boot` + `test` 5 ficheros / 29 tests → 6 / 52 · check con 0 errores bajo src/ (89 en web/, ledger intacto) · docs:check 0/0 · suite entera 459 ficheros / 5389 tests, exit 0. Mutaciones del constructor, cada una restaurada byte a byte: `pins` fuera del reenvío 4 rojos (2 de ellos en la suite de delta: prueba de que ya pasa por el camino real) · sin escape de `<` 2 · sin envoltorio 1 · sin validación 10 · ejecutor vuelto a eval 1 · gramática sin `^` 6 · `storageKey` sin reenviar 2. Adversarial Opus independiente (árbol intacto al terminar): gramática = el ABNF exacto, 0 discrepancias en ~600 000 entradas contra un oráculo propio; suite sin dependencia de orden en 7 semillas; 3 defectos del lote cerrados en una segunda ronda — la frase absoluta sobre CSP en cuatro sitios, y dos mutaciones que sobrevivían (clave en minúsculas; regex con flag `g` según el orden), re-ejecutadas después por el coordinador: rojo en orden por defecto y con las semillas 42 y 777. Filas PREEXISTENTES que destapó el adversarial, no tocadas aquí (van al handoff para el autor): en cualquier build de servidor empaquetado `renderUixBootScript` lanza ENOENT al leer el bundle; la receta de la guía pasa `event.locals.nonce`, que Kit nunca rellena (el navegador bloquea y vuelve el flash, sin error); y el `html.replace` con cadena interpreta `$'` y puede cerrar el script. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
3 weeks ago |
|
|
6e0decb9ed |
feat(motion)!: una fuente de motion, y los cuatro ejes visuales SIEMPRE en prefs — eidos no lee medios, nadie en el árbol uix pregunta al SO, ningún esquema del app deja a eidos sin motor (P2 #5)
La preferencia efectiva de motion la decide prefs UNA vez (`resolveMotion`: la
intención `allow|reduce` gana al hint del SO, `system` deriva) y la proyección la
estampa en `<html data-motion>` — desde
|
3 weeks ago |
|
|
f875f60b97 |
feat(eidos)!: un escalar es una instancia CLAVADA — mueren el throw de la puerta 2 y las fuentes por eje (c')
Cierra el estado INTERINO de
|
4 weeks ago |
|
|
e3c0899dd8 |
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente tras hidratar (app.html sin script, build estatico sin servidor) — flash claro para todo usuario dark — y no podia existir un script de arranque porque no habia contrato de persistencia (PrefsIntentStorage lo aporta cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme / modeSource / densitySource / scalingSource por opciones, sin persistir), por doctrina escrita en arts/prefs/README.md («UIX does not turn it into prefs.theme»). El autor REVOCA esa doctrina. UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron, `resolveTheme` de $libs/theme, que ya existia como gemelo de `resolveMotion`); `theme` (id de familia), `density` y `scaling` las declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) — arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto `ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una funcion pura `resolveThemeId(theme, mode, isRegistered)` (src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a src/uix/eidos/lib/attrs.ts y los de la proyeccion a src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los comparte. UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave `uix.prefs`, espejo del documento de eidos (kind + version estrictos, version desconocida se RECHAZA). Solo intencion — nunca efectivo ni entorno. Adaptador localStorage canonico creado en la raiz standalone (`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA: el bridge awaitea `load()` aunque sea sincrono y produciria una primera resolucion con defaults y un salto un tick despues — el flash movido de sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como `intent`; el bridge queda solo para persistir (skipHydrate). UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js, 15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de sincronia, precedente generated/base.css). `renderUixBootScript(params)` (boot/render.ts) es Kit-agnostico: el framework entrega el string; el hook de Kit son tres lineas de la app (receta en docs/theming/guide.md, NO cableada: el layout congelado lleva persistencia propia). Estampa los NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme data-mode data-density data-scaling) → DELTA CERO al hidratar, probado por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos) y por tres mutaciones (sin hidratacion sincrona → rojo; constante de attr cambiada en un solo lado → rojo; boot compilado con `data-modo` → los tres casos rojos). Hallazgo del test de delta cero: la raiz no aplicaba el entorno del navegador (esperaba que el app llamara detectBrowserEnvironment en onMount) — ahora lo siembra ella (guardado por `document`) y observa sus cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el listener que eidos tenia en createSystemColorSchemeSource. Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 · suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) · check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary, arts/blocks/packs/rtl/translations/agent:check OK. ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md): `resolvePreferences` LANZA con `uix` + escalar/source (forma firmada originalmente); medido despues, `ActiveEidos.create()` inyecta `uix` SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios, incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente `options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276 codifica la doctrina revocada; esbuild no es dependencia declarada (transitiva de vite) — decision del autor con servidores parados. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
4 weeks ago |
|
|
e2c37f8920 |
feat(eidos)!: un tema DICE lo que es — appearance obligatorio en ThemeDefinition, una sola fuente de verdad
|
4 weeks ago |
|
|
e16c14dec7 |
feat(eidos): el ritmo de revelado sube a config — primitives.motion.staggerViewport, y su regla emigra a la foundation
Firma opción 1 del autor (cableado EidosConfig.motion → CSS, la última entrada de PENDING_PRIVATE_RENAME). Ejecutada con el esquema completo: expediente + dos constructores en serie + adversarial, todos Opus en ultra, coordinación y supervisión de la sesión. El gancho --motion-stagger-each-default era FICCIÓN: nadie lo declaraba y su único consumo (motion.css:42) resolvía siempre al literal 70ms. Ahora: primitives.motion.staggerViewport: '70ms' (tipo + default + validación + contrato + emisión --motion-stagger-viewport en :root, molde floating-gap verbatim) y la regla [data-stagger] > [data-animation-trigger='viewport'] EMIGRA a renderMotionBlocks — donde vive la máquina entera del stagger. Con ello motion.css queda en sus dos gates de opacity (su veredicto structural, más verdadero), la entrada del registro cayó STALE POR MECÁNICA y el registro, a cero, se DESMONTA con acta (un guard que inspecciona el vacío es un falso verde); la historia completa cierra en canon/recipe-contract.md. E14 (70→20ms) queda APLAZADA con nombre: firma de diseño propia, mueve 8 blocks. Adversarial 9/9 CONFIRMADO: neutralidad término a término contra la línea base histórica en Chrome real con el IntersectionObserver disparando (0/0.07/0.14/0.21/0.28/0.35s en /blocks/team/preview) - override 999ms reescribe la cascada - nombre muerto inerte (5s en :root, cero efecto) - <Cascade> intacto con control positivo quirúrgico (solo el hijo estampado toma el token) - cero empates de hoja posibles (tres escritores en .css, especificidad decide) - el guard del espacio cerrado muerde por mutación - 126 tests, censo 73%/66/1152-0-0, docs 0/0. Sus reservas, con acta en changelog §57: el contrato gana DOS filas (--motion-stagger-each entra como (derived) al emitirse — clase preexistente de index/index-rev) - gates divergentes latentes - path del contrato sin pin. Correcciones del adversarial incluidas: dos citas muertas del ledger de blocks anotadas, el presente falso de component-audit.ts, y el absoluto de canon matizado. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
1 month ago |
|
|
c494fb3952 |
refactor(soma)!: §15 — la geometría del posicionador es canal del ANFITRIÓN
Firma A del autor (§14 literal, cuarta aplicación de «la capa es la pluma,
no la dueña»): FloatingContentOpts gana `component` (kebab del morfo, nunca
literal — 11 hilos de creación en 9 anfitriones) y las medidas por instancia
del posicionador aterrizan como `--_{host}-floating-{transform-origin,
available-width,available-height,anchor-width,anchor-height}`. El sexto
nombre que el expediente no contaba (`--floating-native-offset`, rama
nativa) queda canal INTERNO de capa (`--_floating-native-offset`: lo escribe
la capa en su propio wrapper, lo leen solo sus reglas foundation). El lector
COMPARTIDO (preset scale-fade de motion) lee el alias neutro
`--_floating-transform-origin` que cada receta anfitriona declara desde su
canal — cableado sliding-indicator, 9 declaraciones. getFloatingContentCSSVars
(export muerto, next-features §13.ii) MUERE con la firma.
Matiz que la ejecución destapó: el content de MENUBAR compone DropdownMenu —
su canal es --_dropdown-menu-floating-* y el alias de menubar se acota al
PANEL (el canal sigue al componente que POSEE la composición flotante);
split-button y palabras leen el canal ajeno de su composición en el punto de
composición, anotado en cada sitio.
Verificado: suites 96+60+78 en verde - eidos-lint x10 con 0 invalid -
reach-floor 5/5 (ledger 0 NEW / 0 STALE) - component:audit 162 PASS / 4
NEEDS-WORK idéntico - check 0 atribuibles POR FICHERO - dinámica CDP 9/9
anfitriones: canal vivo con px por instancia, los nombres VIEJOS computan
VACÍO en todos, positivo exacto (tooltip max-block-size 525.033px == canal
525.0326px; combobox ídem; panel de menubar anchor-width 886px = la barra;
sidebar popout ENGANCHADO 720px/35px), negativo fijando los viejos en :root
= nada se mueve. Trampa reconfirmada: el popout SIN ANCLAR da undefinedpx —
comprobar que la superficie estaba abierta.
Viaja con atribución declarada (acuerdo por canal con la sesión P0
vicen-42, precedente
|
1 month ago |
|
|
69d69d43c6 |
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 |
|
|
4cb76403a0 |
docs(theming): la GRANDE EJECUTADA y verificada 12/12 - TONE_UNREACHED ha muerto
Cierre de la firma de instrumento grande. La verificacion adversarial
confirmo los 12 claims (aritmetica del ledger cargando el modulo en cada
commit, monotonicidad E2E con comparador propio, seis catas empiricas
incluida la sorpresa de tags-input dos veces y el test de dos caras del
PALETTE_FLOOR, la sonda sana, y la estatica fina del codigo).
- Corrige el acta de la retirada de tags-input en el ledger: la asimetria
del estampado (data-color en todos los nodos vs solo el proveedor) EXISTE
pero NO es la causa de la revivida - medido en forma de produccion, el
token mueve 6 nodos igual porque el privado hereda del proveedor al item.
- Anota en next-features SS13 las tres cegueras como EJECUTADAS (S1/S2/S3
con sus commits y numeros) y registra la deuda nueva del adversarial:
asimetria del estampado, ternario falsy de la restauracion de atributos
(
|
1 month ago |
|
|
3bf7930baa |
fix(theming): el velo del sistema no muere por un atajo - y el pin de chronos sobra
Dos hechos de cascada, hermanos de la firma 55:
1. Las reglas del suelo de popover usaban el ATAJO `background:` - expande a
`background-image: none` y MATA el velo de hover del sistema
(archetypes) cuando el orden de carga cae del otro lado. La regla dura lo
prohibe por escrito ("hover is the system's"). Partido a
`background-color:`: 0 px sobre las 14 identidades del catalogo (68
lecturas reposo + 68 hover, todas a cero; `rules split=2` en las catorce
como anti-vacio; control negativo 5/4), y el velo SOBREVIVE al orden
invertido - verificado con experimento de cascada aislado.
2. El pin de `chronos.css:417` RETIRADO: sus cinco declaraciones eran copias
textuales de `[data-button]` - 20 combinaciones variant x size identicas
con y sin el (control negativo 0/20). La adjudicacion de 13 era FALSA:
al `+N more` el velo se lo mata `button.css:69`/`:88`, con pin o sin el.
`calendar-select.css:14` NO es pin (vivo en 3 de 4 tallas) - se queda.
- Guarda extendida (active-eidos-config.test.ts): el suelo declara
`background-color` y NUNCA el atajo - validada por 4 mutaciones (las dos
ultimas prueban la mitad NEGATIVA, anadidas porque 1-2 solas la dejaban
sin morder).
- Sondas popover 0/448 y chronos 0/36.832 - eidos 443+1 ajeno - check
identico byte a byte antes/despues - docs-check 0/0 x2 - lint 0 invalid.
- Registro: changelog 56 (la prueba citable es el barrido de catalogo) -
13: `background-image` RESUELTO y `transition` como RESIDUO VIVO con su
numero (suelo `background,border-color` vs arquetipo
`opacity,background-color`: hoy el border-color del hover SALTA en vez de
animarse) - fichas de chronos (7 filas stale corregidas, recuentos
contados contra fichero: 202->198, 50->47), calendar (su HALLAZGO ya
resuelto por 55, cerrado) y emoji-picker - 5 textos stale ANOTADOS sin
reescribir (cada medida fue correcta con la cascada de su dia), con dos
matices: en gradient-picker solo caduco la mitad base, y open-trigger-fg
gana ahora por ESPECIFICIDAD, no por orden.
- Fuera del registro del eje, por regla dura del autor: el material de
sitio/demo (la inversion de imports de /alpha) - ademas desactivado por
esta misma firma en la capa que importa.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
466b24f34f |
feat(theming)!: el sobre de trigger de popover es un SUELO - tercera ley 55
El "baseline button envelope" que popover.css pinta a sus triggers vivia a (0,2,0) con hover a (0,5,0): le ganaba a las recetas de sus HUESPEDES (el chip de anadir reaccion de chat-message perdia 9/9 propiedades disputadas y salia cuadrado gris entre pildoras - SU receta lo pintaba en el mismo selector-lista y PERDIA), mataba la capa de estado del sistema en hover (chronos plain = caja gris) y dejaba el trigger de calendar en manos del orden de carga (14/16/14/14px medidos - la moneda al aire). El sobre baja a :where() - base y hover, con el :not(field-trigger) DENTRO (where anula especificidad, no matching). El suelo pinta lo que el huesped CALLA; la receta gana donde habla; la capa de estado compone otra vez. Tercera aplicacion de la ley firmada (12.9 planos - B'/53 paleta - y archetypes.css:9-21 ya lo decia para SU capa). Cambio de codigo: 2 lineas. Medido (gate de dos niveles, 76 instancias, 16 rutas, 4 estados): - No afectados: 3 field-trigger diff VACIO; los 66 desnudos ganan EXACTAMENTE los dos valores del SISTEMA que el sobre tapaba (velo de hover de archetypes + su transition). 167 nodos de referencia: 0. - Los 7 movers, valor a valor por CDP hacia lo que SU receta declara: el chip vuelve pildora (36->26px, r9999) - chronos recupera su plain y su hover - calendar mes/anio pasa a su ghost compuesto Y SE VUELVE DETERMINISTA (8 cargas en dos ordenes, mismo pixel; antes 2 de 6 caian del otro lado) - emoji/palabras/ntp a sus recetas (ntp revive 4 claves: 44->48/62). - Capturas antes/despues x8 revisadas por el supervisor (chip, chronos, calendar, emoji, gradient, popover generico, palabras, ntp). - Guarda 55 en active-eidos-config.test.ts validada por SEIS mutaciones (la sexta se anadio porque la primera pasada dejo pasar un verde falso; y el arnes mintio con -t sobre 0 tests - detector de cero-tests anadido). - vitest 35/36 (rojo ajeno skin-media-player) - check 0 atribuibles - docs-check 0/0 - eidos-lint popover/chat-message 0 invalid. Registrado en 13 y fichas: los parches-pin de chronos/calendar pasan a REVISABLES (ahora redundantes o semi) - el suelo comparte peldano (0,0,0) con archetypes.css (medido 16 cargas: archetypes cae despues y el resultado es identico, pero es un empate real sin verificar en produccion) - la dependencia sin registrar de palabras queda anotada junto a su regla (no tiene README de eidos). changelog 55 escrito (la guarda citaba una seccion que debia existir). CON ESTA, LA LISTA "Lo que espera TU FIRMA" DEL CONTINUE QUEDA VACIA. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
ffa45726fd |
feat(color)!: una palabra, un significado - primary/secondary son SIEMPRE jerarquia
La homonimia muere: `primary`/`secondary` significan JERARQUIA DE MARCA en el
catalogo ENTERO, tambien dentro de los seis primitivos de tinta (text, heading,
display, code, label, s-text). El paso de tinta del 82 % se llama `subtle` -
PROP y TOKEN, sin excepcion: el nombre es el contrato en el artefacto (la misma
ley que la FIRMA B' y la 12.9; se rechazo por escrito la via "solo el prop con
tabla de traduccion" - las excepciones legitimas son las FIRMADAS con razon,
no las inventadas al decidir para abaratar).
- Los seis CONTENT_INK, IDENTICOS: {subtle, muted, disabled, on-solid}; el
nivel-1 es el DEFAULT sin prop. `label` gana `on-solid` (cierra la
divergencia de familia registrada en
|
2 months ago |
|
|
8244cb4a62 |
feat(theming)!: FIRMA B' - la cascada de paleta es una ESCALERA sin empates
suelo :where([data-{c}]) (0,0,0) < forward [data-{c}]:where([data-color],
[data-color-custom]) (0,1,0) < tono [data-{c}][data-color='X'] (0,2,0).
El orden de emision deja de decidir: la especificidad ES el contrato,
legible en el artefacto (doctrina hermana de la FIRMA 12.9).
- Emisor (render-css.ts): isPaletteSlotToken PARTE el bucket host - solo
las ranuras de paleta bajan al suelo, el chasis del componente queda a
(0,1,0). 49 suelos + 49 forwards reescritos + 0 pares viejos; bloques
por tono (112), capa compartida y gradient finish byte-identicos.
- Neutra en pixel, MEDIDO: ~297.000 valores computados antes/despues
(105.248 planos lotes A+B, 57.720 planos lote C, 134.464 con los tonos
ESTAMPADOS en las 49 unidades, incluidos 5 @active), 0 diffs reales.
Los 17 crudos, probados ruido por reproduccion sobre codigo identico.
- Guarda por MUTACION (active-eidos-config.test.ts): suelo con contenido,
forward de un peldano, par viejo ausente, tono sin envolver, particion
del host, cierre estructural (toda declaracion de paleta vive en un
peldano) - 6 mutaciones inyectadas, 6 mordidas. El contrato del forward
pasa de vigilar 2 recetas a 49 (privadas incluidas, multi-parte bien).
- Ledger: PALETTE_SUPERSEDED muere (describia el mundo viejo); 207 de sus
353 claves pasan a VIVAS y las 146 restantes quedan bajo PALETTE_FLOOR
(el tono-default estampado resuelve por el forward: quitar el atributo
ES hablar en silencio) o TONE_UNREACHED (ningun paso del guard estampa
tono Y hoverea), con razon medida. stepper y tag-group, barridos por
primera vez. El detector STALE es CIEGO a patrones: la retirada fue
manual y por estrechamiento, guiada por medida.
- Docs: changelog 53 - reference THM-2 - next-features 13 (RESUELTA) -
CONTINUE-theming.
Supervision: 4 agentes Opus en 5 fases (baseline plano + baseline
estampado, emisor, guarda, verificacion de parque completo, docs), con
refutacion del supervisor sobre censo, emisor y guarda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
5d71d830e9 |
feat(theming)!: FIRMA §12.9 — el plano de profundidad es el SUELO, no el techo
El plano `data-depth` pinta el bundle de elevación (superficie · borde · sombra
· tipografía on-surface) y hasta hoy lo hacía a (0,1,0), la misma especificidad
que la receta del componente. La Decisión 8 daba por bueno ese empate porque
«las recetas cargan después de la fundación y ganan por ORDEN DE CASCADA».
ESA PREMISA ERA FALSA
La fundación la inyecta ActiveEidos en runtime como <style> gestionado, y las
recetas llegan como chunks code-split de Vite: a igual peso ganaba quien
cargara el último, y se midió AL REVÉS en dev que en producción (§13). No era
una decisión de diseño, era una moneda al aire — y la moneda cayó del lado del
plano.
LO QUE COSTABA, MEDIDO
Un toast `risk` y uno `fulfill` vestían la MISMA TARJETA GRIS: los seis tonos
idénticos y la franja de acento —el rasgo que identifica la intención de un
vistazo— reducida a 0,67 px de gris neutro, porque el atajo `border` del plano
pisaba el longhand `border-inline-start` en los cuatro lados. Las TRES variantes
de tooltip computaban lo mismo: `outline` indistinguible de `solid`, `ghost` un
backdrop-filter invisible tras una superficie opaca. Y ~35 claves públicas
quedaban adjudicadas como mudas en cinco componentes, con tres recetas que
habían RETIRADO su tipografía por esto.
LA REGLA
La regla de apariencia del plano se emite envuelta en `:where(...)` —
especificidad CERO. La receta gana donde el componente HABLA, en cualquier
orden de carga; el plano sigue pintando todo lo que el componente CALLA, que es
exactamente lo que significa «baseline». Vale igual para un plano que añada un
tema (jaula abierta).
EL FROST NO BAJA, Y ES DELIBERADO
`[data-depth='{plane}'][data-frost]` conserva sus (0,2,0). El baseline es un
suelo que la receta puede pisar; el frost es una petición explícita por
elemento —alguien escribió `data-frost`— y honrarla significa ganarle al fondo
propio del componente. Mismo atributo, intención opuesta: queda comentado en el
emisor para que nadie los «armonice», y el guard lo fija por los dos lados.
QUÉ MUEVE DE PÍXEL, ENTERO
De 19 adoptantes, DIECISÉIS con diff CERO sobre ~49.000 valores computados. Los
tres que se mueven son la firma haciendo su trabajo:
- toast: 35 diffs (7 fondos + 7 bordes de tono + 21 anchuras). Cuatro tonos
medidos en la demo real: 4 de 4 DISTINTOS donde antes 3 de 3 eran idénticos,
y la franja de acento pasa de 0,67 px de gris a 3 px del color de la
intención.
- tooltip: sus tres variantes vuelven a distinguirse — solid opaco con sombra,
outline transparente con borde, ghost translúcido al 70 %.
- popover, tooltip y link-preview: `line-height` de 1.25 (el `--leading-ui` del
plano) al que cada receta pide. Es EXACTAMENTE la decisión que la receta de
link-preview tenía escrita desde el 2026-08-21: «se quedan declaradas A
PROPÓSITO; retirarlas arreglaría el empate a favor del plano — una decisión
de píxel que pertenece a la firma pendiente de §12.9».
`font-family` no se movió en ninguno: las recetas piden el mismo
`--style-label-font-family` que el plano pinta, así que la seguridad
tipográfica del portal (Decisión 8) queda intacta.
Refutada una hipótesis propia: temía que el atajo `background` del plano
estuviera matando el velo de hover del sistema y que bajarlo lo resucitara
moviendo píxel. `backgroundImage` no cambia en ninguno de los 19 — el velo vive
en los ítems, no en la superficie.
EL LEDGER
17 claves salieron STALE solas y se retiran: toast (3), tooltip (5),
float-panel (5), link-preview (2), popover (2). Los cinco vuelven verdes —
toast pasa de 51/85 a 72/85. El ledger sigue en 87 bloques, sin duplicados, y
parsea.
DOS VECES QUE EL INSTRUMENTO MINTIÓ
La sonda compartida mide UN nodo en dialog, drawer, popover, tooltip,
context-menu, dropdown-menu y link-preview: nunca abre la superficie, que es
justo donde el plano pinta, así que sus «0 diffs» no probaban nada. La medición
buena reproduce el «antes» EN RUNTIME, inyectando la regla vieja a (0,1,0) al
final del head en vez de revertir el fichero — entra como
`scripts/__plane-open.mjs`.
Y el primer barrido del ledger dio 87 de 87 rojos: «todos fallan igual» es la
firma del instrumento, y era la misma trampa del CR de Python en Windows que ya
está registrada. Re-corrido con la lista limpia.
EL BARRIDO DEL LEDGER ENTERO
87 de 87 componentes con entrada en el ledger, re-corridos tras el flip: cero
NO EFFECT sin adjudicar, cero STALE, cero errores. El cambio es global y se
verificó globalmente.
GUARDS
vitest src/uix/eidos 439/440 (el rojo conocido skin-media-player) ·
active-eidos-config 75/75 · npm run check COMPLETED con los 72 errores
preexistentes de la rama y ninguno en ficheros de este cambio · rtl:check 0 ·
docs:check 0 · eidos-lint 0 invalid en los cinco tocados · capturas de las tres
superficies que se mueven.
Un test hermano afirmaba el selector desnudo para un plano añadido por un tema:
ahora afirma `:where()`, porque un plano de tema es un suelo igual que los
cinco de fábrica.
Doctrina en docs/theming/changelog.md §29 (junto a la Decisión 8 que corrige),
cierre del expediente en next-features §13 y handoff al día.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
7781d6e4c8 |
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa
El canon de nombres llevaba meses documentado y sin guard, y la medición del
2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %.
Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que
vuelva.
R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que
comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`,
modificador interactivo DELANTE, y detrás lo dimensional y contextual.
No reimplementa la gramática: la consume de `theming-census --names`, que es
la misma fuente sobre la que corrió el codemod. Dos implementaciones de una
gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa
factura con `commit-resize`, un hook muerto tres meses en una receta con todos
los tests en verde.
Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo
pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se
reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y
renombrar lo condenado es churn.
Muta-prueba de tres caras, que es lo único que distingue un guard de un guard
que pasa sobre el vacío:
`-bg-hover` con valor de acento → ROJO
`trigger-color` → ROJO
`primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone
el modificador detrás por construcción)
La tercera es la que importa: es el fallo que un codemod ingenuo habría
cometido sobre las 47 claves de rol, `button` entero incluido.
Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le
faltaban (tinta y estado) más la frase que las gobierna y las dos familias con
gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que
acota el principio de plataforma del px/py a los ejes dimensionales. El
checklist de cierre declara la regla — lo cazó `docs:check` con su propio
guard I5, que exige que toda regla del audit esté declarada allí.
Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde
`PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los
hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run
check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando
la migración a la capa de estado las vacíe; son dos líneas entonces.
component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres
sin tocar por esto, R-5.3 pasa en los 166)
docs:check 0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
740274a847 |
uix(background): el fondo se mueve con el scroll, y con lo que el lector no pidió no
F3 (parallax) + F4 (demo y registro documental) + las correcciones de sus dos
auditorías, en un commit porque viven en los mismos ficheros: la demo enseña los
ejes que F3 añade, y separarlas dejaría un estado que nunca se probó.
Cinco maneras de que una capa deje de estarse quieta: `speed` (cuánto del token
de travel cubre mientras el anfitrión cruza el viewport), `bleed` (crece más
allá del anfitrión para que el viaje no arrastre su propio borde), `depth` (la
deriva contra el puntero), `spotlight` y `attach='fixed'`.
El travel es CSS: `animation-timeline: view()` lo gobierna desde la posición de
scroll, sin listener ni rAF. Sólo donde el motor no lo trae, la pila arranca
`ScrollProgress` y escribe `--background-progress`, que la rama `@supports not`
mete en la MISMA declaración; los dos caminos no pueden estar vivos a la vez
porque el JS comprueba la condición idéntica con `CSS.supports`, y ambos se
paran bajo `prefers-reduced-motion` — el parallax es movimiento atado al scroll
del propio lector, que es justo la clase que provoca síntomas vestibulares.
**La decisión que no estaba prevista.** El scroll y el puntero quieren mover la
MISMA capa, y una animación sobre `translate` gana a cualquier declaración
estática: el puntero habría dejado de existir sin más. Así que el scroll anima
una custom property REGISTRADA (`@property`, o interpolaría a saltos) y un único
`translate` compone los dos términos. Medido: parallax solo → `0px 30px`; con el
puntero arriba-derecha y `depth: 20px` → `20px 10px`. Es `translate` y nunca el
shorthand `transform`, la misma ley que sigue el lift del draggable con `scale`.
El precio, dicho porque en la primera redacción escribí lo contrario tres veces:
una custom property NO se puede compositar, así que el navegador recalcula
estilo cada frame. Para una decoración es el intercambio correcto —una property
por capa que viaja, ninguna bajo reduced motion— pero «va en el compositor» era
falso y ahora el código dice lo que ocurre.
**Dos footguns cerrados por forma, no por disciplina:**
- `attach='fixed'` se DECLARA desde la capa y la pila se recorta sola. Antes
había que escribirlo también en la pila, y olvidarlo dejaba la capa
`position: fixed` pintando a sangre por todo el viewport, detrás de todo y sin
error (un hijo fijo se escapa de `overflow: clip`; sólo un `clip-path` lo trae
de vuelta). La prop de la pila desaparece: no hay nada que olvidar.
- Un `speed` negativo —una capa que se mueve contra el scroll— invertía el
bleed: la capa ENCOGÍA y enseñaba justo los bordes que el bleed tapa. Ahora
usa la magnitud.
**Lo que costó medición**: el shorthand `animation` pone `duration: 0s` y una
línea de tiempo de progreso necesita el `auto` inicial, así que con el shorthand
la capa no se movía nunca (van longhands, con el porqué escrito) · mi listener
de puntero pedía un frame y no lo liberaba si el rect salía degenerado, matando
el puntero para el resto de la sesión (reescrito sin frame, con el rect cacheado
e invalidado por `pointerenter` y `observeResize`) · las cuatro registraciones
—`animated`, `pointer`, `scroll`, `fixed`— comparten un solo sitio,
`declare.svelte.ts`, donde vive la regla A30 y su segunda mitad: registrar desde
el init, y seguir el prop sin escribir en la primera pasada.
**La demo** (`/uix/components/background`, v2, nueve pestañas) monta un
ANFITRIÓN de verdad en el escenario, porque este componente es invisible por sí
solo y sin padre no se puede enseñar lo único que importa: que el padre se
adopta y el layout no se mueve. Los chips son uniones completas verificadas por
el TIPO (`Record<Union, 0>`): un miembro que falte es error de compilación.
Y fue la demo la que destapó que, con A30, encender `animate` en caliente no
hacía aparecer el control de pausa — el registro era un hecho de montaje.
Invisible en una sonda, obvio con un interruptor.
Registro documental (D-BG.11): `next-features.md` §11 · la frase en
`design-text-effects.md` (el mismo corte canon/pack leído desde el otro lado) ·
`PLAN-blocks-quality.md` Q0.3 → sucesor · `surface/README.md` §Gaps «scrim de
autoría» CERRADO por `Background.Scrim` · glosario con entrada `Background` y
`Aura` corregida (decía «Not built yet» y está construido) · y en
`motion-guide.md` §8 + el RFC: el travel ligado al scroll no es un preset —un
preset nombra una transición discreta CON duración, y esto es modulación
continua sin ninguna— y sólo se replantea como dominio con un segundo consumidor.
Verificado en Chrome real: el puntero mueve `depth` y `spotlight` con los
valores exactos y vuelven al centro al salir · `attach='fixed'` estampa y
retira el recorte de la pila · el bleed aguanta el speed negativo · RTL: el
`translate` del puntero se mantiene FÍSICO y el bleed en el eje de bloque · cada
control de la demo cambia algo (los de `spotlight` y `depth` no llegaban a tres
de las cuatro clases de capa hasta la segunda auditoría).
⚠️ SIN VERIFICAR, y no lo doy por bueno: el travel real al hacer scroll, los 60
fps y el detector de reflow. El panel del navegador va oculto con viewport 0×0 y
ahí las animaciones scroll-driven declaradas en CSS no se activan — comprobado
que es del ENTORNO con un caso mínimo inyectado (un `div` pelado con
`animation-timeline: view()` sale inactivo mientras una `ViewTimeline` creada
por API sobre el mismo sujeto marca 68%). Necesita una pasada con Chrome
visible.
Gates: audit `--only background` PASS 0 errores · eidos-lint invalid 0 ·
`rtl:check` 0/180 · `docs:check` 0/0 en 634 docs · `blocks:check` 0/18 ·
`morfo:check` PASS · smoke PASS · 441/442 (el fallo es el `skin-media-player` de
siempre) · `check` 0 errores propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
ad26922d29 |
uix(foundation): data-ink, el contexto de tinta para marcas ajenas
El hermano de data-on, para un problema distinto: uno re-entinta un subárbol porque cambió su lienzo, este porque el contenido son marcas de otros y se pelean entre sí. Un muro de logos de clientes es el caso que todas las referencias shippean y ninguna resuelve a nivel de sistema: veinte marcas en veinte paletas gritan sobre la página, así que los catálogos retocan cada asset a mano — y por eso venden el modo oscuro como un segundo artefacto, porque un asset retocado no puede seguir a un tema. Va a la fundación y no a un componente, y eso se midió antes de elegir. Image no tiene eje de tinta, ni prop ni regla, y tampoco era su casa: es un componente con máquina de estados de carga para fotos, mientras que un logo suele ser un svg en línea. Y el mecanismo es doble — un vectorial toma la tinta por currentColor y un raster por filtro —, así que un solo eje tiene que cubrir los dos. Un primitivo Logo nuevo se descartó por lo mismo: no habría cargado más que el contexto. Sin tokens nuevos: el reposo y el realce son los que el tier semántico de la escala de opacidad ya tenía. Y el opt-out por marca vale anidado, porque hay marcas registradas que no se pueden alterar y el bloque no puede saber cuáles. Dos medias querys que importan: la transición se anula con la preferencia de movimiento reducido, y bajo colores forzados se retira el desaturado entero, porque ahí la paleta es del sistema y desaturar pelearía contra el contraste que ese modo existe para garantizar. Verificado sobre ocho marcas de anchos y colores dispares: en mono, gris al sesenta y cinco por ciento con la tinta del tema, y otra distinta en oscuro; en brand, el color propio de cada una; al pasar el puntero, color entero. Queda anotada en el generador la trampa que me costó una vuelta: renderBlock une declaraciones con un salto de línea y no añade punto y coma, así que cada entrada trae el suyo salvo la última. Escritas sin ellos, las reglas llegan a la hoja de estilo y no pintan nada. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
93d18c90df |
uix(table): una tabla ancha vuelve a tener por dónde salir, y expone los tipos que exige
Dos cosas que salieron al componer la tabla de comparación de pricing, y que no son del block. La primera se ve y se mide: a 375 la tabla ocupaba 420 dentro de un contenedor de 327, y esos 93 píxeles eran inalcanzables. Sin barra, sin indicio, columnas desaparecidas. Fijar la primera columna —que funciona— no sirve de nada si no hay nada que desplazar. El envoltorio recortaba en los dos ejes: el recorte existe por el radio de las esquinas y el eje de bloque lo necesita, pero el eje en línea no, y ahí se comía contenido. Ahora el eje en línea desplaza y el de bloque sigue recortando, así que el radio se conserva y nada scrollea en vertical salvo que se pida con maxHeight, que es otra cosa. Medido después: cuatrocientos veinte sobre trescientos veinticinco, desplazable; al mover noventa y cinco la columna fijada se queda donde estaba y la última columna entra en pantalla. La demo del propio componente no cambia. La segunda es de tipos: el componente exige una instancia del motor y no la exportaba, así que cualquiera fuera de libs tenía que cruzar una frontera para tipar su propia envoltura. El guard del tier blocks lo rechazó por nombre y tenía razón: lo que se corrige es que el componente exponga lo que demanda, no que la frontera se ensanche. El guard afirma la forma con su porqué escrito y está probado por mutación: devolviendo el recorte de siempre, falla. Que sea guard y no nota importa porque el fallo era silencioso — nada peta, el contenido desaparece por el borde. Quedan registradas dos filas abiertas que no toco: la columna fijada se ancla con una propiedad física, así que en dirección derecha-izquierda se mueve con el contenido en vez de quedarse —medido, su borde se sale de la pantalla—, y eso es del eje de dirección; y el prop de la tabla no lleva parámetro de tipo, así que todo consumidor tipado castea, empezando por la demo de este mismo componente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b92dc3bdb2 |
uix(card): el estado deshabilitado deja de pelearse con la animación por la opacidad
Salió midiendo el plan actual de pricing: la tarjeta no se atenuaba. La regla estaba escrita, el token resolvía a 0.4 sobre la propia tarjeta y el cursor de esa misma regla sí aplicaba — pero la opacidad computada seguía en uno. La culpable es su propia animación de entrada. Corre con relleno en los dos extremos y su último fotograma es opacidad uno; una animación gana a una declaración normal en la cascada, así que fijaba la propiedad para siempre. Aislado en el mismo nodo desactivando la entrada, atenúa correctamente. Llevaba ahí desde que el componente existe, invisible porque el cursor hacía parecer que el estado estaba cableado, y alcanzaba a toda Card deshabilitada del ecosistema. El arreglo es dejar de compartir la propiedad: el estado atenúa por filter, que la animación no posee. Mismo token, misma escala, ninguno nuevo, y el bloqueo del puntero en la tarjeta interactiva no se toca. El guard afirma la FORMA con su porqué escrito —que la regla usa filter y no opacity, más el bloqueo del puntero y la presencia de la animación que obliga a esa forma— y está probado por mutación: devolviendo la regla a opacity, falla; restaurada, verde. Verificado con la animación presente: la deshabilitada computa filter opacity a 0.4 y sus hermanas ninguno; en píxeles, su tinta más oscura sube a 164 frente a 139 de la normal sobre el mismo lienzo, que es justo lo que da un 0.4 sobre el fondo de página. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d61515027a |
docs(theming): el changelog vuelve a su formato — mi entrada no traía 400 líneas
Al escribir la §49 pasé prettier por el fichero y reformateó los ejemplos de código de entradas ajenas: 237 inserciones y 185 borrados de los que apenas veinte eran míos. Es justo lo que el handoff del tier advierte de los ficheros que ya venían con otro formato — formatearlos produce un diff del documento entero y sepulta el cambio real. El fichero vuelve al formato que tenía y la entrada se re-aplica sin pasar prettier. Efecto neto contra el estado anterior al commit: 43 líneas, todas de la §49. No reescribo el commit porque la rama es compartida. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d38c54fdf8 |
uix(card): el realce al puntero deja de ser rehén de `interactive`
El usuario preguntó por qué una tarjeta de plan de precios no responde al puntero. Le dije que Card no exponía estado de puntero y era falso: el realce existe desde siempre —los dos tokens están en la fundación, con reglas para ghost, outline y solid, composición con el anillo de selección para que no desaparezca al pasar el ratón, y anulación bajo reduced-motion—, pero todo colgaba de un solo attr. Y ese attr empaqueta cinco cosas: el elemento button, el evento commit-select, el cursor, la escala de pulsación, el anillo de foco y la elevación. Una tarjeta que ya contiene su propia llamada a la acción no puede tomar ese paquete: un button dentro de otro es marcado inválido y las dos activaciones se pelean por el mismo gesto. Así que la elevación era inalcanzable justo donde más se pide — planes de precio, teasers de artículo, tarjetas de testimonio. La elevación pasa a su propio eje. `interactive` lo implica, así que una tarjeta clicable no cambia en nada. Lo que el eje NO trae, a propósito: ni cursor de puntero —un puntero sobre algo que al pulsarlo no hace nada es una promesa falsa—, ni escala de pulsación, ni anillo de foco. Eso es lo que un CONTROL le debe al visitante. Sin tokens nuevos: los dos que hacían falta ya estaban. Y el attr es de envoltorio visual, no contrato — lo estampa el componente de eidos y lo guarda la mitad de presencia, que gana su primera fila de card; no el morfo, porque declarar ahí lo que sólo lee una capa es justo lo que la doctrina del 15 de agosto dejó dicho. El eidos-lint lo clasifica como eidos-only, 0 inválidos. Medido en navegador sobre estilos computados y posición real: apagado, el hover no mueve nada ni pinta sombra —cero regresión—; puesto, translateY de −2px y sombra de 18/48, con la tarjeta todavía div, cursor auto y su CTA intacto dentro, sin un solo botón anidado en botón; bajo reduced-motion el desplazamiento se anula y la sombra se queda, que es suprimir el movimiento sin perder la señal de profundidad; y una Card interactive de verdad —cinco en la demo del canon— sigue siendo button, con cursor de puntero y los mismos dos píxeles de elevación. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
f1b672da79 |
docs: la doctrina alcanza a los cambios de la jornada — cinco backlogs y una llave de mas
Auditoria de deriva doc<->codigo sobre los 33 commits del 2026-08-13/14: cinco sitios seguian describiendo lo que el codigo dejo de hacer ayer. Se corrigen COMO BITACORA, al final de cada fichero (el cuerpo es el acta de lo que se firmo, no el estado del codigo; reescribirlo falsearia la firma). El formato es el que ya existia en `arts/adom` y `soma/textarea`: `## Backlog`. - `decisions/book-deviations.md` — dos entradas. D.7 nombraba `IntentExpectedFamily`, tipo que M6 ( |
2 months ago |
|
|
6f42eebfe8 |
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
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>
|
2 months ago |
|
|
49362d89e9 |
feat(sema,sound): un pack se entrega como quieras — y un `data:` no se pide, se descodifica
La entrega deja de ser parte del contrato. Un pack de ficheros puede llegar
como quince URLs o como UN JSON de base64 (`packFromBase64`): mismo tipo, mismo
intercambio en caliente, misma reserva sintética por entrada, y un nombre que
no existe sigue sin compilar. Medido sobre `static/sounds/ui-inline.json`: una
petición en vez de quince, y 69.082 B con brotli frente a 79.332 B de los
ficheros sueltos — el +33 % del base64 lo deshace la compresión, y el JSON gana
además porque nadie comprime `audio/mpeg` y todos comprimen `application/json`.
## `step` no se oía
Era a la vez el MÁS CORTO (18 ms) y el MÁS FLOJO (gain 0.025), y esas dos cosas
se multiplican: el oído integra energía durante ~100-200 ms, así que un tono
corto necesita MÁS amplitud, no menos. Medido, rendía a −29,8 dBFS con la
novena parte de la energía de `touch`. Ahora 0.06 a los mismos 18 ms.
Lo que no había era el guard: la distinguibilidad es una RELACIÓN, y dos
sonidos pueden ser perfectamente distintos entre sí y ser los dos inaudibles.
`sound-names.test.ts` gana un suelo absoluto sobre `gain² × duración`.
## El precalentamiento no precalentaba
- `preload` pedía el contexto con `getOrCreateContext()`, que hace
`await resume()` — y antes del primer gesto esa promesa se queda PENDIENTE en
Chrome, no se rechaza. No descargaba nada hasta que el usuario ya había
pulsado, que es justo la latencia que el preload existe para quitar.
`decodeAudioData` funciona en un contexto suspendido.
- Aplicar un pack en caliente no calentaba la caché: cada nombre pagaba su
viaje la primera vez que sonaba. Ahora `rebuildMap()` llama a `warmSamples()`.
- Con `preferences.sound === 'off'` no se descarga NADA — un pack que no se
puede oír son bytes gastados en silencio. Pero la petición se RECUERDA en vez
de tirarse: el nivel se lee en el dispatch, así que la primera reproducción
audible vacía la cola y encender el sonido no devuelve una caché fría.
## Un `data:` se descodifica en el sitio
`connect-src 'self'` BLOQUEA `fetch('data:…')` — la directiva casa por ESQUEMA
y `'self'` no cubre `data:` — mientras deja pasar una ruta del mismo origen. Un
pack inline enrutado por `fetch` caía a síntesis EN SILENCIO justo en el
entorno que llevó a alguien a hacerlo inline. Y es ~6x más lento. `bytesFor()`
lo resuelve con `atob`.
## De la revisión adversarial
- Las claves del JSON se validan: una que no sea `SoundName` LANZA, igual que
una ruta mala en `assertMapPaths` y por la misma razón — tragarse la errata
da el síntoma «parte de mi pack no se aplicó» sin nada a lo que apuntar.
- `isSoundName` preguntaba `name in SEMA_MAP.sounds`, y `in` recorre el
prototipo: `isSoundName('toString')` devolvía `true`. Inofensivo mientras
todos los llamantes tecleaban el nombre; nada inofensivo al validar red.
- `warmSamples` sólo tenía una de las dos ramas que sí tiene el constructor, así
que una app con canal de sonido propio se calentaba al arrancar y nunca más;
y esa rama leía la preferencia UNA vez, en construcción. Las dos se caían por
lo mismo: ahora comparten `warmUrls()` en vez de duplicarse.
- El demo del catálogo cancela su fetch: elegir `inline` y saltar a `synth`
antes de que llegara reinstalaba el pack sobre un `clearMap()` ya hecho.
- `fallbackFor(name)` borra los dos casts que copiaba todo el que autorase un
pack de ficheros.
## Documentación
`architecture/sema.md` gana la sección de las dos formas de entrega y el
párrafo de `preloadSamples` reescrito — describía un precalentamiento que ya no
es el que ocurre. El README del arte conoce la ruta `data:`. La página de packs
gana su sección equivalente y deja de señalar unos `.wav` sueltos como «el
ejemplo».
Y un fantasma: `soft` no existe — es un nombre del catálogo viejo que quedaba
como ejemplo en ocho sitios, incluido el docstring de `applySounds`, cuyo
ejemplo copiado literalmente LANZABA.
`static/sounds/ui-inline.json` se genera desde `packs/ui-mp3.ts`; el comando
está documentado ahí y reproduce el fichero byte a byte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
e0265bed95 |
feat(eidos,sema): un tema es UNA cosa — siete ejes, y ninguno fuera del alcance
Cierra el eje: el haptico entra al mapa, `masterGain` deja de descartarse, y `applyTheme` gana el septimo eje. ⚠️ CORRIGE MI PROPIA PROPUESTA. Dije que la puerta unica iba en la raiz de composicion. Al medirlo, la dependencia corre AL REVES: `ActiveEidos` tiene `#uix` y lo conduce (registra presets con `uix.motion`), mientras `ActiveUix` no tiene eidos. Un tema compuesto en la raiz habria invertido la direccion. Va donde ya vive el metodo que se llama a si mismo «the capstone composing them», y que hasta hoy componia seis de ocho. eidos.applyTheme({ color: '#3b5bdb', sound: { soft: { gain: 0.08 } } }); eidos.clearTheme(); // revierte los siete Un eje que pasas se aplica, uno que omites revierte — la misma lectura atomica que ya tenian los seis visuales, ahora sobre siete. Sin `uix` (render headless) el eje perceptual se salta y los visuales siguen: un tema que se aplica a medias es mejor que uno que revienta. EL HAPTICO, AL MAPA. Su tabla `kind → pattern` vivia como const dentro del canal, asi que era el unico eje perceptual que un tema no alcanzaba — exactamente la asimetria que el trabajo del sonido existia para quitar, dejada en pie un canal mas alla. Ahora es `SEMA_MAP.haptics`, con perfiles derivados (`floorMs` + `scale`/`gapMs`) o constantes (`pattern`): los tres kinds evaluativos son ritmos constantes porque lo que hace reconocible a un `error` es su cadencia, no su longitud. El canal lo lee por GETTER, no por copia, asi que un `applyMap()` en caliente le llega sin re-registrarlo. `masterGain` SE APLICA. Pasarlo con motor compartido se tiraba en silencio (S-05/S-32) pese a ser una perilla viva del motor y la forma natural de poner el volumen desde la raiz. Ahora llega a `master.setGain`, y de las opciones que de verdad no pueden aplicarse a un motor ajeno se AVISA en vez de desaparecer. Y `DeltaValue` pasa a exportarse: es la forma de toda semilla de override, sin ella un consumidor no puede tipar el tema que esta pasando. Su ausencia era un error VIVO en el estudio de sema, que lo importaba y no podia — por eso check BAJA a 73 desde el baseline de 75. VERIFICADO: theming 8/8 (incluye re-vocear el haptico y que la tabla autorada quede intacta) · sema+eidos+morfo 802/802 · check 73 (baseline 75, −2 por el export) · docs:check 0/618 · suite completa 7 fallos, los mismos ajenos y preexistentes de contracts.test. ⚠️ Nada de esto se ha OIDO ni TOCADO en un dispositivo real: todo esta medido con el resolver y las suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c5b394574c |
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales
El autor: «que yo no pueda definir a nivel de tema la personalizacion es un
fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia.
NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de
personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX);
sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin
forma de revertir. Un `applyTheme` unico construido encima habria sido peor
que dos puertas honestas — una llamada donde el color cambia ahora y el
sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la
ultima y aqui va el sustrato.
1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y
el mapa es la estructura que un tema direcciona. Mientras vivio fuera como
const de modulo —error mio de ayer— el VOCABULARIO era el unico eje
perceptual que un producto no podia re-vocear a ninguna hora. El resolver
pasa a resolver el nombre contra el mapa que recibe, no contra un const,
que es lo que hace que un tema surta efecto.
2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en
vivo y revertibles, reconstruyendo desde el mapa autorado, asi que
aplicar dos veces es aplicar una. Misma forma que los seis applyX de
eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo
surte efecto en la siguiente ocurrencia sin re-arranque.
3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…`
dejaba el valor real intacto y hacia crecer un fantasma al lado, en
silencio; y descender a traves de una hoja primitiva la sustituia por un
objeto. En una API de theming eso es intolerable — el sintoma es «el
sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en
construccion, no solo en el retuneo.
LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es
`keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no
puede inventar uno que ningun componente pueda referenciar. La voz se abre;
las palabras no.
⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa
`as const satisfies SemaMap` daba las claves literales que `SoundName`
necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo
recorren en generico (el estudio de sema: +5 errores en check). El catalogo
pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha.
⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo
esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera
emision del mismo evento. La memoria de frecuencia funciona.
VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte,
re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos
rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline ·
docs:check 0/618.
QUEDA, y es decision de producto no defecto: la puerta unica
`uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores:
`masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica
`kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico
eje perceptual que un tema no alcanza.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
1302f28a5d |
docs(sema,theming): el orden real, como se autora cada canal, y el hueco del theming
Tres cosas que el cambio del sonido dejo sin plasmar.
1. EL ORDEN CANONICO MENTIA. La numeracion 1·2·3·4·5a·5b esta citada como
canonica en sema.md, resolver.ts y engine.ts, y despues del reorden era
falsa para `sound`: un pack ya no autora en 5a, lo hace en 1.5. Corregido
en los tres sitios a la vez, con el porque — todo lo que AUTORA un sonido
corre ahi conservando su precedencia relativa; solo cambio su posicion
respecto al intent. El resto de la regla (channels/haptic/hold) mantiene
el «gana el ultimo» porque no son ejes evaluativos.
2. COMO SE AUTORA CADA CANAL (CANON §7). El reparto por dueño estaba
escrito; lo que un componente ESCRIBE, no. Tabla nueva: el canal visual
no se escribe (se declara el evento y eidos reacciona), `sound` es UN
NOMBRE, `haptic` es un `kind`. La regla es la misma en las tres filas y
ese es el punto: un componente dice QUE ocurre, nunca cuan fuerte, cuan
brillante ni cuanto dura. El sonido era la excepcion hasta ayer.
3. EL ANALISIS DEL THEMING que el autor pidio (theming/channels.md §5b).
Lo que el cambio NO toco: nada de eidos. El canal visual se proyecta
estampando data-event-*, y esa ruta quedo intacta — ni una receta, ni un
token, ni un selector. Eidos no sabe que existe el sonido y no le hizo
falta. Lo que cambio fue DONDE se autora (un catalogo en vez de 71
ficheros) y CUANDO se aplica (antes del intent), ambos dentro de sema.
Lo que si cambio, y es lo util: `applyTheme(seed)` retunea SEIS ejes
visuales (color·type·depth·shape·space·gradient) y el sonido tiene CERO.
Antes esa asimetria se justificaba sola — con 214 reglas autorando 33
firmas a mano no habia objeto que retunear. Hoy hay exactamente uno:
16 nombres en un const. Un eje `sound` en ThemeSeed seria la misma forma
que los otros seis.
Y las tres puertas de personalizacion, medidas, ninguna llega al
catalogo: `overrides.runtime` recorre rutas de SEMA_MAP y el catalogo no
esta en el mapa (ademas se traga las erratas, S-09); `overrides.cascade`
es por selector, no un tema; `masterGain` se descarta en SILENCIO
(S-05/S-32). Un producto puede silenciar y puede pisar una ocurrencia,
pero NO puede re-voceear el sistema. Queda escrito en vez de ser
folclore.
⚠️ chronos: `src/uix/sema/components/chronos.ts` entro en la migracion de
`769e426c5` pese a ser de escritura excluida. Era forzoso — con el tipo
estrechado, dejarlo sin migrar rompe la compilacion del arbol entero — y
el cambio es mecanico (soundTuning(...) -> nombre), sin decision de diseño.
Su SPEC.md NO se ha tocado y sigue citando `soundTuning('commit.soft')`.
VERIFICADO: sema+morfo+sound 437/437 · check 75 · docs:check 0/618.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
432106f1f6 |
docs(sema): la doctrina deja de mentir — deriva propia, copias podridas y dos leyes muertas
Pasada de saneamiento documental tras los tres commits de sonido, con el
informe de AUDIT-sema-2026-08-05 como mapa. Tres clases de defecto.
1) DERIVA QUE YO MISMO DEJE (lo mas urgente)
El 05 renombre las claves silenciadoras a `commit.silent` / `emerge.silent` y
el 06 las elimine del catalogo — dejando media docena de docs citando claves
que ya no existen, y afirmando ademas comportamientos que ya no ocurren:
- switch / toggle / toggle-group README: decian «silent-by-default
(`commit.silent`)». Hoy es `commit.medium` (gain 0.1) y suenan. Corregidos
con el nivel real y su razon (un tercio de una pulsacion de boton).
- tooltip README + docblock del pack: decian que el tuning «resta el gain de
la familia, asi que neutral emite a 0» y que «threat / fulfill siguen
aflorando». Ambas cosas son falsas desde el 06: es `SILENT`, el resolver
retira el canal y NADA aflora, porque no queda ganancia que subir.
- toggle-group apuntaba a `LIBRO_VARIACIONES_Y_EXTENSIONES.md`, que es un stub
movido; ahora apunta a book-deviations.
- La nota del renombrado en D.5 decia «hoy es `commit.silent`»: una clave que
vivio UN DIA. Marcada como tal, con la lista de las que siguen vivas.
2) COPIAS PODRIDAS — la ley del corpus es enlazar, no copiar, y estas dos
entradas la incumplian
- D.9 transcribia `SEMA_HOLDS_BY_INTENT` entera y se habia quedado atras: D.12
corrigio DOS valores el 2026-07-06 (`commit.fulfill` noticed→settled,
`signal.loss` noticed→brief, ambos contra el texto del libro) y la copia
siguio afirmando los viejos un mes. Sustituida por el puntero a holds.ts +
el enumerado generado, con la leccion escrita en el sitio.
- sema.md transcribia `SEMA_VERBS` y le faltaban DOS verbos vivos:
`commit.unselect` y `handle.zoom`. Retirada; queda el puntero a verbs.ts y a
vocabularies.md, que si se genera y tiene guard de frescura.
3) LEYES QUE LA PRACTICA YA HABIA DEROGADO, Y NADIE REGISTRO
- El contrato `emit` publicaba `Promise<void>`; devuelve `Promise<string>`
desde D.9. Corregido, y explicado que ese string es el id de la ocurrencia —
el unico asidero para cerrar una senal persistente con `clear`.
- channels.md fijaba «3 canales runtime» y D.8 cerraba la puerta a Announce
(«hoy no»). El `AnnounceChannel` existe desde el 2026-07-04: built-in,
opt-in y exportado. channels.md pasa a 4 con la distincion que importa
(visual/sound/haptic EXPRESAN; announce SUSTITUYE) y D.8 queda marcada
PARCIALMENTE SUPERSEDED.
- sema.md prescribia `{ announce: uix.announce }` — y eso NO COMPILA: las dos
firmas no casan (bolsa de opciones vs posicional). Ahora ensena el adaptador
de una linea que si compila, y declara que ninguna raiz cablea el canal hoy,
asi que activarlo cae en el fallback que anade un SEGUNDO par de live
regions. El arreglo de codigo queda sin tomar: es decision de diseno.
- La cabecera de engine-sound.ts y la fila de arts/README seguian afirmando
como MECANISMO que «`prefs.sound` mapea al bus ui», que la auditoria AU-4 ya
habia corregido en el README del propio arte: lo garantizado por
construccion es el NEGATIVO (ninguna politica de UI escribe el bus content).
AMBIGUEDADES QUE NO RESUELVO PORQUE SON TUYAS, pero que dejan de estar
escondidas: D.7 prohibe canonizar samples fuera de `signal` y
`proof-of-human` los usa en dos eventos `commit`, aplastando la modulacion por
intent que esa misma entrada existe para proteger —y el comentario del pack
afirma literalmente lo contrario de lo que hace—; y D.8 declara que los packs
deben respetar el `activeChannels` de la familia mientras sema.md prescribe lo
contrario y el pack de dialog deja dos reglas inertes. Ambas quedan marcadas
con su estado real y los dos caminos excluyentes, pendientes de tu firma.
VERIFICADO: docs:check 0/616 · sonido + sema 244/244 · cero referencias vivas a
las tres claves muertas (las que quedan en book-deviations son historia
declarada como tal, y las de cronica no se reescriben) · prettier: los 6 docs
con avisos ya estaban sucios en HEAD, no los toco.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
15621632be |
fix(color): un solo vocabulario de rol, y la documentación del color reconciliada
Había DOS tipos exportados con el mismo nombre. config-types.ts:39 declara
`ColorRole = HierarchyColorRole | Intent` derivado del const (9 roles, con
tertiary) y existía desde el 2026-05-13; eidos/lib/types.ts:125 lo re-escribió
a mano siete días después con 8 miembros —sin tertiary— y nada lo justifica:
ni el commit (
|
2 months ago |
|
|
3097cfcb69 |
revert: deshacer la auditoría entera — se hizo sin leer la doctrina
Revert de los 7 commits de la sesión del 2026-07-29/30: |
2 months ago |
|
|
c39170abbf |
fix(uix): la auditoría del sistema — lo que los guards no veían
Auditoría clean-room de todo ActiveUIX (excluido `web/`), componente a
componente. Lo que sale de aquí no es una lista de bugs: es un patrón.
El framework validaba que lo escrito fuese VÁLIDO, no que lo declarado
se CUMPLIESE — y sus guards fallaban ABIERTOS.
## El colapso de las uniones de props (95 → 0)
Un `Props` de eidos es `{ …props propias… } & <atributos nativos>`.
Cuando el elemento declara un atributo homónimo, la intersección funde
ambos y una unión estrecha contra el `string` nativo COLAPSA a `string`.
Causa: `Without<T, U> = Omit<T, keyof U>` invocado como `Without<T, {}>`
— `Omit<T, never>`, un no-op — 433 veces en soma; sólo 3 con argumento
real. Invisible para `svelte-check`: ensanchar un tipo no es un error,
es una garantía perdida.
Medido: 95 props en 72 componentes. `<Avatar color="nonsense">`
compilaba. `ComboboxInput.size` chocaba con el `<input size>` numérico
y era inusable. Migrado con codemod sobre AST (nunca regex) a
`Own & Omit<Nativos, keyof Own>`: 92 tipos en 73 ficheros + carousel a
mano. `check` no se movió.
Garantía nueva: `eidos/prop-surface.test.ts` (PROP-1) compara los
literales de la anotación del autor contra los de la propiedad pública.
Verificado que falla reintroduciendo el defecto.
## Los cuatro guards que fallaban abiertos
- `translations:check` crasheaba en CADA ejecución de su historia — un
stripper de comentarios borraba `//` dentro de strings. Sustituido por
import dinámico. Al arrancar destapó 8 slots `texts` sin traducción.
- `soma-attr-audit` agotaba el timeout de 5 s: sin veredicto, verde por
omisión.
- `component-audit` D-7.4 hacía `continue` mudo cuando el tipo no
resolvía. Ahora resuelve con el checker de TypeScript
(`scripts/prop-unions.ts`): puntos ciegos de 124 → 3.
- `component-audit` R-1.1: el regex casaba `[data-motion='reduce']` y
daba PASS por el motivo equivocado.
Regla adoptada: un guard que no puede evaluar TIENE que decirlo. El
informe lleva ahora bloque «Not verified» y recuento en el resumen.
## D-1 · tooltip y D-2 · card, cableados
`tooltip` declaraba 3 eventos `emerge` que nadie emitía. Ahora emiten;
`present` pasa a `sequence: 'post'` — con `'pre'` el hold de ~240 ms
gateaba el montaje del propio overlay.
`card` declaraba `commit-select` sin emisor posible (scope sin soma).
Puente headless en `soma/components/card/` con la forma ya establecida
por `menu-dial` / `onion-menu`: eidos posee estado y render, soma posee
sólo el `SomaRuntime` que emite.
## Documentación: 22 mentiras corregidas
`docs/` afirmaba guards inexistentes (`NO_MISSING_PROVIDER_TESTS`),
APIs con firma equivocada y un modelo de Motion que el código no
implementa. Corregido en CANON, arquitectura, glosario, theming/motion,
checklist y los README de `motion` / `callout` / `arts/motion`.
## Además
- CardGroup: la descripción se metía en la primera celda del grid.
- Motion: `data-state` siempre estampado, salida real en `leave()`,
token fantasma `--motion-stagger-each-default` eliminado.
- `engine-motion`: `handoffState` Map → WeakMap (fuga por nodo).
- `mockup`: primitivo crudo → token de rol (R-4.6).
- 5 catálogos de traducción que faltaban.
Handoff: `docs/process/CONTINUE-audit-2026-07-29.md`.
Batería: check 0 errores en src · vitest server 3701/3701 ·
docs:check 0/0 · component:audit 161 PASS / 2 NEEDS-WORK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
79a90e6283 |
fix(eidos): las variables de `Flex` y `Grid` dejan de heredarse
Dentro de un `Mockup`, un `<Grid columns={7}>` colapsaba a columnas de `0px`: el
grid medía 72px, exactamente los 6 huecos de 12px, sin nada para las 7 pistas.
La §46 registró `inherits: false` para las 44 variables de `Box`, pero `--flex-*`
y `--grid-*` se quedaron fuera. Como una custom property hereda por defecto, el
`align="center"` de un `Stack` exterior escribía `--flex-align: center` y CADA
descendiente lo heredaba: un `Stack` anidado que no pide alineación ninguna
acababa con `align-items: center`, su hijo `Grid` encogía a contenido, el ancho
quedaba indefinido y `1fr` no tenía nada que repartir.
Las 7 variables de `flex.css` y las 13 de `grid.css` se registran ahora con
`@property { syntax: '*'; inherits: false }` y sin valor inicial, igual que Box.
Un layout anidado vuelve a decidir su propia alineación.
Verificado: `vitest src/uix/eidos` 361/361 · el grid del mockup pasa de `0px` a
`103.71px` por columna, que es exactamente `(798 − 6×12) / 7` —el reparto real—
· demos de hero, pricing, feature-split, testimonials, button, accordion y table
sin regresión. Doctrina en theming/changelog §48.
Lección registrada: al arreglar la herencia de un recipe hay que barrer TODOS los
hermanos que escriben variables para sí mismos. El «pon `justify` explícito en
los clusters anidados» que yo había anotado en el handoff era este bug sin
diagnosticar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
2 months ago |
|
|
cd399f7c7c |
fix(eidos): el `align`/`justify`/`alignContent` de `Grid` sí aplican
`<Grid align="center">` no tenía efecto: `align-items` computaba `normal`. Los atajos `place-items`/`place-content` del recipe se emiten DESPUÉS de los longhands, y su fallback `revert-layer` —cuando el prop `placeItems`/ `placeContent` no está puesto— revertía el longhand anterior a su valor inicial, pisando en silencio lo que `align`/`justify`/`alignContent` escribían (un atajo posterior gana sobre el longhand que expande). Ahora el fallback de los atajos COMPONE los vars de los longhands en vez de revertir; si el prop del atajo sí está puesto, sigue ganando (va el último). Los grids por defecto no cambian (para ítems de grid, `align-items: normal` ≡ `stretch`). Encontrado al alinear las filas de `feature-split`. Verificado: `vitest src/uix/eidos` 353/353 · en el navegador, feature-split (filas centradas), el chart del Mockup (`align="end"` = barras a la base) y feature-grid (AutoGrid, sin cambio). Doctrina en theming/changelog §47. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
d38bd63faa |
docs(blocks): registro de F2.1, doctrina de demo del tier y handoff
Documentación al día de lo que se cerró hoy y handoff para retomar mañana.
- `PLAN-blocks.md` §7: **F2.1 `site-header` HECHA** con sus siete commits, la
fase 0 contra el dossier §P1, el landmark que le faltaba a `NavigationMenu`
(arreglado en el canon, no parcheado en el block), el hueco del CTA que
navega y parece botón (registrado, no falseado) y el defecto de framework
que destapó la demo.
- `theming/changelog.md` §46: las 44 variables de cascada de `Box` dejan de
heredarse. Un `Section` regalaba su padding a cada descendiente —la galería
arrastraba ~300px de aire desde F0— y los hijos heredaban anchos y `display`
ajenos. `@property { inherits: false }`, radio verificado sin regresiones.
Lección: una variable que un componente escribe para SÍ MISMO debe declararse
`inherits: false`; si no, deja de ser un prop y se vuelve un contagio.
- `architecture/blocks.md` B-9: la demo de un block se construye sobre el
harness compartido y el block se enseña A SANGRE — nunca dentro de un marco
con relleno ni de una caja con scroll, porque eso cambia lo que el block
hace.
- `src/uix/blocks/README.md`: anatomía de la demo (harness, `{Name}Site`, ruta
`preview`, `DocRow`, catálogo único, ejes en el shell).
- `CONTINUE-blocks.md` (nuevo): handoff — qué toca (F2.2 `hero`), la plantilla
de ficheros para copiar, las reglas que ya costaron sangre (a sangre, iframe
solo para anchos de dispositivo, cada prop un control, nada de backticks en
`<Text>`, ojo con las variables que heredan), la deuda declarada que es
decisión del usuario y el estado exacto de los gates.
Gates al parar: `blocks:check` verde · `svelte-check` 73 errores, todos deuda
ajena (0 propios) · `vitest src/uix/eidos` 353/353 · `contracts.test` con los
3 fallos ajenos conocidos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
5aa8090bad |
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto (steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo gamut-mapping), así que toda escala desde donantes §40-compliant cumple por construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía nada que resolver → descartado por especulativo. D2 = base verbatim (sin migración base→seeds). - $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº mágico), border·7 exento - scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40), consume CONTRAST_PAIRS + banco de regresión morph-generado - eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia - docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
b2f0a7f2bf |
docs(theming): ratifica la doctrina de contraste tonal — Stage 1 (paridad)
Cierra Stage 1 de la iniciativa next-features §1 con los veredictos del usuario: - reference.md §40 (doctrina standing): contrato de contraste por pares de slots. Texto = dos tiers (text·11 secundario ≈APCA 60 / text-strong·12 AA garantizado). Bordes = dos tiers WCAG 1.4.11 (decorativo exento: accent·7, subtle·4/default·6, reposo-sobre-fondo · portador 3:1: solid·9 lo cumple, el resto con señal redundante). Foco = eje de config (primitives.focusRing + color.focus, un solo `outline` por §32); default suave, endurecer = valores por config, no código. - changelog.md §44: el chronicle datado (drift medido + veredictos + la herramienta scripts/contrast-audit.ts). - next-features.md §1: Stage 1 marcado DONE; Stage 2 (generador by-construction) sigue abierto, gated en la migración base→seeds. Sin cambios de código (el hallazgo del foco es una decisión de valores-por- defecto vía config; defaults mantenidos por decisión del usuario). El script de auditoría (commiteado antes) queda como herramienta de medición. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
f0de4faf75 |
docs(color): handoff de cierre definitivo de la jaula del color
Actualiza el handoff y el changelog §43 al estado FINAL de la iniciativa (cerrada, cuarta sesión): - open-color-cage-2026-07.md: añade las 2 fugas transitivas cerradas (card-group-item · s-text) + el guard runtime, el fix del bug de Card/Avatar (`b93cc6c5c`), la lista completa de commits (sesiones 3–4), y una sección «Follow-ups» con lo menor que queda (override docs-chrome sobre `<Code>`, ringColor roles-only, límites de los 2 guards, comentarios históricos) + nota del estado del working tree (WIP de chat/palabras sin commitear, ajeno). - changelog.md §43: el bug de Card/Avatar pasa de «flageado, NO arreglado» a ARREGLADO con el resumen del fix (migración a resolveComponentColor + compose del seed + canales ring/badge separados intactos). Sin cambios de código. La iniciativa queda CERRADA sin huecos abiertos (qr-code excluido por diseño). Contract 30/30. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
72a6569c3e |
uix(color): abre `color` en la familia chart (resolver SVG rol/escala/crudo)
Chart era el último hueco de la iniciativa. Su `color` NO tinta labels/ejes:
tinta la MARCA de datos (stroke de línea, fill de área/barra/burbuja, hue de
heatmap, arco de gauge, porción de pie/funnel/bar-list/polar, punto de smith).
Es SVG con resolución JS, así que NO usa el `data-color` + capa de paleta
compartida (eso es para cascadas CSS en HTML) — el mecanismo es propio.
Resolver central en `chart/context.ts`:
- `seriesColor` / `seriesSurface` reescritos + `seriesContrast` nuevo, todos
`number | ComponentColorProp`. Un índice cicla `SERIES_ROLES` (multi-serie,
sin cambios). Un valor explícito pasa por `resolveChartColor`:
· rol/intent → `var(--color-{role}-{solid,surface,contrast})`
· una de las 33 escalas → `var(--scale-{name}-{9,a2})` (steps solid /
surface-alpha del `PALETTE_SLOT_STEP`); contrast → white (no hay token)
· valor CSS crudo → verbatim (surface = `color-mix 15%`, contrast = white;
sin contraste garantizado, el trade-off de cualquier custom).
- `ChartContext.color` widenado a `number | ComponentColorProp`.
Todos los props `color?: ColorRole` (~14: ChartSeriesProps, Bubble, Sparkline,
ChartCategory, BarList, Funnel, Calendar/Heatmap, Smith×2, Gauge) →
`ComponentColorProp`. Los 3 builders que armaban el fill inline (funnel:
solid+contrast, heatmap:56, calendar-heatmap:118) refactorizados para pasar
por el helper — funnel pierde su `roleOf` local.
qr-code queda FUERA por decisión de diseño (color = tinta de módulos del QR,
contraste con el fondo, no encaja escalas).
Verificado en Chrome: las 3 ramas del resolver resuelven a color real
(--scale-teal-9→oklch, --scale-teal-a2→rgba translúcido, #3b82f6→verbatim,
mix translúcido ok); marcas existentes = primary-solid (roles behavior-
preserving). check chart limpio; contract 30/30. Docs: changelog §43 +
handoff actualizados (hueco chart cerrado, qr-code excluido por diseño).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
0b2170d626 |
uix(color): Fase 5 — guard estructural + docs (CIERRA la iniciativa)
Guard: test nuevo "keeps every component `*Color` prop open (THM-2 — the
cage stays open)" en recipe-css-contract.test.ts — escanea los alias
`export type XColor = …` de components/*/types.ts y FALLA si alguno se
estrecha por debajo de ComponentColorProp. Uniones aditivas ('muted',
'inherit', 'absent'…) pasan; alias resuelven transitivamente
(DatePickerColor = CalendarColor); WIP_TRACKS (chronos) re-entra con su
track; exento comentado: OnionColor = string (MAS ancho que la jaula,
normalizacion pendiente). Verde con el inventario real: 60+ alias abiertos.
Docs (todo lo aplazado del track):
- reference.md §25: reversion de los subconjuntos THM-2 registrada como
doctrina (color = sistema completo en TODOS; identidad ≠ evaluacion;
enforcement = el guard).
- Tracker clean-room §THM-2: nota de reversion (los narrows que THM-2 dejo
"por diseño" quedan abiertos, Avatar.Badge incluido).
- changelog.md §43: cronica de la iniciativa completa (mecanica, fases,
huecos señalados).
- Handoff open-color-cage-2026-07.md → CERRADO con el resumen de la tercera
sesion (cola completa, F4, F5, commits) y los 2 huecos pendientes de
decision: chart (11 props ColorRole) y qr-code (color inline semi-abierto,
invisible para el guard). El cuerpo queda como referencia de patrones A–E.
- 13 READMEs con afirmaciones ya falsas corregidos (roles-only, subsets,
"Locking to ColorRole", tablas de props): badge, css-field, field-langs,
float-panel, link, listbox, mark, metrics (solo el icon; Chart delega en
chart y queda como esta), password-field, proof-of-human (incl. la fila de
decision "descartar" → "hecho (reversion)"), range-calendar, textarea,
timeline. banner NO se toca (su ColorRole es del eje intent, otra cosa).
Contract test: 29/29 verde.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
d990fe42f9 |
eidos(gradient-finish): auditoria final - tinta named determinista via --_{c}-finish-ink (D7)
Re-auditoria completa del ciclo (batteria entera de guards + bordes
discriminantes medidos en navegador). Dos hallazgos, dos cierres:
1. El override directo de --_{c}-fg del named finish EMPATABA (0,3,0) con el
slice solid de Surface y dejaba el ganador al orden de hojas de estilo -
no es un contrato (el caso discriminante aurora-sobre-amber lo destapo:
la primera verificacion usaba primary, cuya contrast blanca coincidia con
la tinta de aurora y enmascaraba el empate). Fix por la doctrina D7: el
generador emite la var --_{c}-finish-ink y el slice solid de cada recipe
la consume con su contrast de fallback - gana por existir, nunca por
especificidad; las variantes no-solid quedan inertes por no leerla.
Verificado por valores: aurora-amber blanco (autorada) en Button/Badge/
Surface; flat/rampa amber oscuros (heredada); outline inerte.
2. Nombre no declarado (gradient="foo") degrada a la rampa - medido y
documentado en D11 como fallback gracioso.
Atribucion corregida de paso: el crash de morfo:vocabulary/morfo:check es el
morfo de text-blur (deuda preexistente ajena, chip de tarea creado) - NO
palabras como se asumio antes; surface/box/palabras validan OK.
Bateria final: eidos entero 331/333 (2 = proof-of-human preexistentes),
config 72/72, guard 5/5, audit 145/145, eidos-lint invalid:0 en los tres
consumidores, typecheck limpio.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
ff32a36a15 |
eidos(gradient-finish): documenta el comportamiento alfa/transparencia (auditoria por valores)
Auditoria de la pregunta "respeta la transparencia todo el sistema": medido por valores computados en navegador. El fill por capas lo hace correcto - - identidades opacas (los 42 roles/escalas) -> acabado opaco (matchea el solid de hoy); - aurora/named = translucido POR DISENO (blobs color-mix(...,transparent) sobre la base solida, alfa 0.62 medido) - la translucidez es el mecanismo; - slots tinte (surface/track a2/a3) + variantes soft: el acabado es inerte, su translucidez se preserva por no leerse; - data-on inks: translucidos intencionales. El unico borde (medido y documentado, no un bug del camino previsto): un valor translucido forzado en el slot solid (no alcanzable por ninguna prop - solid es la variante OPACA por definicion; la translucidez vive en los slots tinte) hace que el color-mix hacia el ancla opaca suba el alfa (0.5 -> 0.545/0.631). La base background-color conserva su alfa, el elemento sigue translucido en conjunto. Entrada fuera de contrato, no defecto - un framework de referencia lo documenta en vez de dejarlo tacito (capitulo D2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
77ef3b0b3d |
eidos(gradient-finish): v2.2 named finishes + on/data-on - EL PLAN COMPLETO
D11 - Named finishes (el diseno A rescatado como opt-in explicito):
- gradientFinish.named opta gradientes del open cage como acabado, cada uno
con su tinta AUTORADA obligatoria (--gradient-{name}-ink, override de la
convencion --_{c}-fg a especificidad (0,3,0) sobre el slice de variante).
- La base sigue siendo el solid de la identidad (fill por capas, D2) -> el
aurora shipped son blobs de rol con alfa SIN color base final:
background-image-valido POR ARQUITECTURA (el caveat del mesh disuelto, no
exceptuado), y re-tine por tema y modo via referencias de rol.
- Honestidad documentada: la validacion numerica al peor stop de un string
CSS arbitrario no es implementable (stops desconocidos); posible solo para
gradientes de modelo (buildGradient) - diferida. El guard clava la sanidad
del config (named subconjunto de gradients + ink presente).
D12 - on/data-on minimo (Surface on="light|dark"):
- La foundation re-vincula --color-content-*/--color-border-default para el
subarbol (dark -> tinta on-solid; light -> on-solid-contrast; mixes oklch
82/64/32%). Verificado EN VIVO: un parrafo muted dentro del aurora computa
la tinta del contexto al 64%.
- Limites POR CONSTRUCCION y documentados en cada consumidor: componentes
anidados con tokens propios y contenido portaleado NO se re-entintan (la
inversion completa sigue siendo iniciativa independiente). forced-colors:
el contexto resuelve a CanvasText/GrayText (bloque extendido). Sin
color-scheme a proposito (solo chrome UA).
Ademas: prop gradient ampliada a (string & {}) para named en Button/Badge/
Surface; poda del orphan-guard (border/text de Surface: 3 slots x 8 colores,
lo que las variantes consumen); guard 5/5; audit 145/145; lab CASO 07 con el
aurora REAL (<Surface gradient="aurora" on="dark">) verificado por valores.
Docs: capitulo D11+D12 + registro D1-D12 + roadmap COMPLETO; reference p39;
changelog p42; plan cerrado (quedan declaradas: inversion completa, demo
propia de Surface, validacion de modelo para tintas named).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
58076cadfe |
eidos(surface): primitiva Surface - el lienzo temable (Box + tratamiento), v2.1
Box es layout-only por doctrina y rechaza background/color; Surface es la primitiva que posee el lado del tratamiento: el caso hero / banda decorativa / bgGradient como primitiva de primera clase, no como escape-hatch style=. - Composicion sobre <Box> (patron Section): hereda TODA la API de caja y estampa data-surface + color/variant/gradient/rounded (attrs eidos-only de wrapper, doctrina D8). - Recipe palette-tint espejo de Card SIN chrome: _palette-* 5 slots x 8 colores -> el forward THM-2 ruta roles + 33 escalas donantes gratis (verificado: color="teal" resuelve sin cableado extra). - Variantes soft (tinte track, tinta global) y solid (lienzo saturado, texto plano hereda contrast); gradient en el gate del acabado (rampa anclada + spread; sin solid-hover -> extremo profundo cae a solid). - Morfo declarativo patron Box (scope eidos, 1 parte, 0 eventos justificados); eidos-lint invalid:0; audit 145/145 PASS (ficha completa: Baseline/Decisiones/Gaps con disposiciones/Passive justification). - Lab temas/gradientes CASO 07: heroes REALES con Surface (rampa primary, spread plum, banda soft teal) verificados por valores computados; el aurora nombrado queda como preview del siguiente paso. Cola v2 restante: named finishes con tinta autorada + on/data-on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
63f6209e5f |
eidos(gradient-finish): v1.5 kind spread - rotacion de matiz anclada, medida antes de implementar
Esta vez en el orden de la doctrina D6b: las dos puertas se midieron ANTES
de escribir la implementacion.
- Puerta 1 (sonda, 84 combos): la rotacion pura a L constante rompia grass
(+-4 grados) y gold (+-27) - L de OKLCH no es luminancia relativa. Rescate =
la propia doctrina aplicada suave: ambos stops rotados toman la mezcla
DEBIL del ancla (lift/3) -> 0 regresiones hasta +-45 grados.
- Puerta 2 (RCS en Chromium, por valores computados): el canal h de
relative color es <number> - calc(h +- 30deg) computa none -> el token
--gradient-finish-spread es SIN unidad ('30').
Shipped: gradient="spread" en Button+Badge (boolean | 'ramp' | 'spread';
data-gradient='spread' overridea la var del acabado - cero cambios de CSS de
recipe); token + override por tema (gradientFinish.spread); guard 4/4 con el
invariante del spread anclado; lab con especimenes reales (threat cruza el
360, gris C=0 queda ~plano - documentado, no caso especial); docs D10 en
theming/gradient-finish.md + reference p39 + changelog p42 + plan v1.5
completa.
Una doctrina, dos kinds: la rampa huye de la tinta con fuerza; el spread
huye suave mientras juega con el matiz.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
a63d806b66 |
eidos(gradient-finish): override del dial por tema/modo - v1.x COMPLETA + capitulo del libro
- ThemeDefinition.gradientFinish.lift: el tema re-emite --gradient-finish-lift
en su bloque (misma especificidad, despues en cascada -> el tema gana);
claro y oscuro pueden llevar intensidades distintas, 0% apaga por tema.
Test: active-eidos-config.test.ts ("per-theme gradient-finish dial override").
- Capitulo de nivel libro docs/theming/gradient-finish.md: registro de
decisiones D1-D9 con el porque de cada una, las alternativas rechazadas con
evidencia (color-value/variante/bg-prop; pasos 7/11 con valores reales;
lift-up global medido y tumbado 52/84), la rectificacion documentada y la
leccion de proceso (medir antes de fijar defaults). Indexado en docs/README.
- reference.md paragrafo 39 enlaza el capitulo; changelog paragrafo 42 y el plan
marcan la v1.x completa (guard + Badge + override por tema).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
322a1939a8 |
eidos(gradient-finish): acabado anclado a la sombra de la tinta - v1 Button+Badge + dial temable + guard
El gradiente entra al sistema como ACABADO (material) del fill, jamas como
valor del eje de color: prop `gradient` -> attr eidos-only `data-gradient`
(familia data-variant; NO morfo - el runtime lo resolveria desde props de
soma y mergeProps clobberea el stamp del wrapper).
- Fill por capas: background-color = base solida (degrada sola en
forced-colors) + background-image = rampa derivada de los slots de LA
instancia (roles + 33 escalas + custom gratis, tinta heredada).
- Dial unico de tema: primitives.gradientFinish.lift (26%) ->
--gradient-finish-lift (0% = apagado; override por instancia via cascada).
- RAMPA ANCLADA a la sombra de la tinta ("la rampa huye de la tinta"):
tinta blanca -> #000 fuerte abajo (CTA sombreado); tinta oscura -> #fff
fuerte arriba (glossy). Ancla+angulo por color x modo con el MISMO flip
del slot contrast. Rectificacion MEDIDA: el lift global hacia blanco
rompia la tinta heredada en 52/84 combos a 26% (techo global 0%) - el
contraste ahora solo puede mejorar: dial sin topes.
- Guard ejecutable gradient-finish-guard.test.ts (3/3): no-regresion <=40%
sobre 84 combos + set flat-fail clavado (cyan/orange, deuda on-solid
preexistente).
- Generador emite la VAR (--_{c}-fill-finish); el recipe pinta (solid +
re-assert en hover: su shorthand background resetea el longhand).
- Lab temas/gradientes: 9 casos con componentes reales sobre el token real
(dial en vivo, polaridad observable, evidencia de pasos 7/11, forced-colors).
- Docs: capitulo theming/gradient-finish.md (registro de decisiones D1-D9,
alternativas rechazadas con evidencia, leccion de proceso: medir ANTES de
fijar defaults) + reference.md paragrafo 39 + changelog paragrafo 42 + plan
docs/process/gradient-finish-plan-2026-07.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |