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 }
532 Commits (468a9d1134d697546f1b36231d6773d6324a5faa)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
b36247944b |
docs(blocks): el plan de ejecución de V1, con la fase 0 corregida
La ficha de Pricing.Compare reservaba dos decisiones y ahora las tiene, resueltas por doctrina y no por gusto: los planes los declara el createTable del app y la parte lo compone —B-5 admite datos sólo donde el canon compuesto ya es data-driven, y Table lo es—, y en móvil la tabla scrollea con la columna de características fijada, porque el canon ya da el pinning. Eso último es una corrección de mi propia fase 0: el README de Table lo lista como gap diferido y es falso a fecha de hoy — el engine, soma y el recipe lo implementan los tres. Me apoyé en el README y di un dato equivocado; el primer paso del plan lo corrige donde vive. Seis pasos con su verificación cada uno, y los riesgos del árbol compartido escritos para el agente que lo ejecute. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
70f45033fd |
uix(background): la pila se cuelga del anfitrión, y el anfitrión ni se entera
El núcleo del componente (F1 de PLAN-background.md): `Background` es la PILA,
no un envoltorio — se renderiza como HIJO de la superficie que viste y se
ancla detrás de su contenido. El padre se vuelve anfitrión por una regla de
foundation, `:where(:has(> [data-background]))`, así que ninguna receta se
parchea y ningún consumidor reestructura su layout. Con la pila entran sus
capas: `Layer` (la ranura del pack), `Pattern` (las 4 de Backdrop más lines,
noise, rings y vignette), `Gradient` y `Scrim`, el contexto de pausa y el
`on` que reata la tinta del contenido de arriba.
Dos cosas medidas en Chrome real que el plan no preveía:
- La regla de anfitrión escribía `position` y no llegaba (D-BG.16). `box.css`
declara `position: var(--box-position, revert-layer)` en `[data-box]`, que
es (0,1,0) y gana a un `:where()`; y sin `@layer` en la hoja, `revert-layer`
devuelve la propiedad al valor del UA. Una Section o una columna de Grid
computaban `static` y la pila se escapaba al ancestro posicionado más
cercano. La regla escribe ahora también `--box-position: relative`: se
resuelve DENTRO del mecanismo de Box en vez de sobre-especificarlo, así que
un `position` por prop —que llega inline— sigue ganando y `Dialog.Content`
conserva su `fixed`. 5/5 anfitriones cubiertos.
- El prop `flex` de Box no crecía a un hijo flex — el hallazgo que el README
del hero dejó anotado el 2026-07-23 sin causa. Es el mismo `revert-layer`:
`box.css` ponía el shorthand `flex:` al lado de los tres longhands, y el
shorthand borraba aquel de los tres cuya var estuviera sin poner.
`<Box flex={2}>` computaba `0 1 auto`. El wrapper expande ahora el shorthand
con la gramática de CSS (`expandFlexShorthand`, pinneada en test) y el
shorthand desaparece de la receta; medido en una fila de 600px:
`flex={1}`→146px, `flex={2}`→292px, `grow` explícito sigue mandando.
Las capas no heredan la paleta del anfitrión: `--palette-*` se hereda, así que
un `Scrim` dentro de un `<Card color="teal">` habría pintado un velo teal en
vez de un velo. Guarda de PRESENCIA, la misma de THM-2 un nivel más abajo — el
scrim resuelve `--color-overlay` y el patrón `--color-primary-solid` salvo que
la capa lleve su propio `color`.
El hero cambia `Backdrop` por `Background.Pattern` como hijo de su Section y
gana las ocho tramas. Su layout `background` sigue con las capas a mano hasta
F2, cuando existan `Background.Image` / `.Video`; el renombre del snippet es
de F5 por D-BG.13.
Gates: audit `--only background` PASS 0/0 · eidos-lint invalid 0 ·
recipe-css-contract · generated-css · blocks:check 0/15 · rtl:check 0/180 ·
504/505 en eidos+morfo+adom (el fallo es el `skin-media-player` de siempre) ·
`check` 0 errores propios.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
f4ce006423 |
blocks(faq): la lista estática, y un tipo que deja de prometer lo que no da
La brecha del dossier para este block, y la mayoría del formato: seis de siete en Tailwind Plus no son acordeón, sino una rejilla de preguntas y respuestas siempre abiertas. El eje vive en la LISTA, no en la raíz. Primero porque la lista es lo único que cambia —la cabecera se lee igual—, y segundo porque esa colocación es la que permite que el tipo diga la verdad: pasa a ser una unión discriminada. Bajo acordeón atraviesa toda la superficie del Accordion del canon; bajo lista no se ofrece ninguna de esas props, porque una rejilla estática no tiene estado abierto que enlazar, nada que colapsar ni modo simple o múltiple. Ofrecer una superficie y descartarla en silencio es justo lo que A-94 dejó dicho que no se hace, y aquí es donde iba a morder después. El ítem lee la disposición del contexto: no puede discriminar sobre la unión de la lista sin obligar a la app a repetir el arreglo en cada pregunta. Su value y su disabled quedan documentados como del acordeón, la misma convención que el fondo del hero y la media del cta. Y bajo lista la pregunta es un encabezado de verdad, porque ahí ES el encabezado de su respuesta; bajo acordeón el canon la mete dentro del disparador y esa semántica es suya, así que el block no inventa una segunda. Medido a 1280: en acordeón, cinco disparadores, ningún encabezado, medida de setecientos sesenta y ocho y respuestas ocultas hasta pulsar; en lista, ningún disparador, cinco encabezados, tres pistas de trescientos nueve, medida de mil veinticuatro y todas las respuestas visibles sin tocar nada. A 375 cae a una columna conservando ambas cosas. El acordeón queda idéntico. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
50a7f47147 |
uix(background): el contrato del fondo existe antes que su primer píxel
F0 de `docs/process/PLAN-background.md`: la infraestructura que las fases siguientes consumen, sin una sola regla de pintura todavía. - **morfo `background`** (`scope: ['eidos']`, 0 eventos): tres partes — la pila, la capa (`aria-hidden`) y la pausa. Ningún knob visual: no hay soma que cruzar, así que cada eje de pintura es attr de wrapper (la llamada que `image.ts` ya registró en su bloque comentado). La pausa es el BOTÓN compuesto, no un contenedor de colocación: el patrón de `fab.ts` — el mismo `<button>` lleva los dos markers — porque `[data-archetype='action']` paga cursor, anillo de foco y 44px de suelo táctil, y eso no se le cuelga a un div. Textos `pause`/`play` con las claves idénticas al catálogo (la trampa de chronos). - **foundation**: `:where(:has(> [data-background]))` adopta a CUALQUIER padre como anfitrión — posicionado y aislado — sin tocarle la receta. Un componente no puede estilar a su padre, y la alternativa (que el consumidor recuerde `position: relative`) es el footgun de las referencias: las capas desaparecen y nada dice por qué. Especificidad 0 a propósito: el `position` propio de un `Dialog.Content` sigue ganando. El bloque D12 gana el gemelo `:has` para que el `on` de un fondo re-entinte al padre que lo hospeda. - **`$adom`** gana los dos puertos que un fondo necesita y nadie tenía: `ScrollProgress` (el 0→1 que `view()` recorre, hasta ahora privado dentro de ScrollFrames) y `prefersReducedData` (la query estándar O el `saveData` de Chromium — el gemelo de PESO del reduced-motion, honrado no descargando, nunca escondiendo contenido). Dos defectos propios, cazados por revisión adversaria y corregidos aquí: la primera medición de `ScrollProgress` era síncrona dentro del `$effect` (la regla 5 de timing — ahora se difiere al frame que el helper ya tenía), y el stub de `matchMedia` ignoraba la query, así que la implementación podía pedir `prefers-reduced-motion` con la suite entera en verde. Medido por mutación: ahora mata 4 de 6. Los tokens `--background-*` NO entran todavía: declararlos sin su receta rompe tres guards de `recipe-css-contract` (medido con sonda, revertida). Viajan con el CSS que los consume, en F1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
f3bfcf2dd9 |
blocks(testimonials): una cita sola no va en una tarjeta
La brecha que el dossier le apuntaba a este block: la cita única es la variante modal de todas las referencias y el grid la minoría, dos de ocho en Tailwind Plus. Entra como disposición, no como hermano. La anatomía no cambia —cita, autor, avatar—, sólo cómo se colocan, y el precedente es el fondo del hero, que también redefine lo que hacen sus partes. El hermano se reabre sólo si algún día pide rotación o carrusel, que es otro componente. Viaja por contexto de configuración, el patrón del align de feature-grid y team, no la coordinación de pricing. Y viaja porque el spotlight no es una piel sobre las mismas cajas: la rejilla deja de serlo y de estampar el ritmo estructural —que es para una FILA, no para una cita—, el ítem pierde la tarjeta, la cita sube de tamaño y se centra, y el autor pasa debajo, donde ya no hay una hilera de caras que alinear. Pedirle a la app que repita esa decisión en cinco partes es el agujero que A-84 cerró en feature-grid. Enmarcar una cita sola la hace parecer un ítem de una lista que no está ahí, así que ahí no hay tarjeta y el tipo lo dice, en vez de aceptar una superficie que luego descarta. La cita se centra con un párrafo de verdad: sobre una caja en línea el centrado no aplica, y sería mentira. Slot nuevo para la marca de quien firma, en las dos disposiciones: es lo que hace que una cita se lea como evidencia y no como una opinión, y es la pieza que ponen todas las referencias. Medido: en grid, cinco tarjetas, veinte píxeles, medida de mil veinticuatro y ritmo escalonado; en spotlight, cero tarjetas, una cita de veintiocho centrada en párrafo, medida de setecientos sesenta y ocho, autor centrado y sin escalonado; a 375 la cita cae a veinticuatro. El grid queda idéntico a antes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
0646d5ebde |
blocks(pricing): el plan dice dónde estás, y el que no puedes elegir dice por qué
Firma del usuario: faltaba la decoración del elegido, y faltaba poder marcar un plan como no disponible — si estoy cambiando de plan, el que ya tengo no debería poder seleccionarse. Lo segundo cambia la posición doctrinal del block. Su README decía desde julio que nada en una tabla de precios puede bloquearse y que por tanto no hay nada que explicar. Eso vale mientras la sección es sólo una comparación; se cae en cuanto el visitante ya está en un plan. Y ahí manda lo que costó contact: una acción bloqueada debe su frase, o es un agujero — algo que no se puede hacer y nada en pantalla diciendo por qué. El plan gana un estado: disponible, actual o elegido. Un enum y no tres booleanos, porque tres booleanos admiten lo que no existe, como elegido y no disponible a la vez. Y ortogonal a featured, que es la recomendación de quien vende: el plan recomendado puede ser justamente el que ya tienes. Se deriva una sola vez en el plan y viaja por contexto. La acción se lo entrega al botón del app por parámetro de su snippet, así que nadie vuelve a deducir en el punto de uso si se puede pulsar, y las palabras del botón siguen siendo del app. La frase, en cambio, es del block: idlangref con respaldo en inglés dentro de un Record exhaustivo, el mismo mapa que bloquea la acción es el que escribe el motivo, así que un estado que bloquea en silencio no se puede ni expresar. Su test lo afirma en las dos direcciones. Y una parte nueva la dice y posee el id al que apunta el aria-describedby del botón deshabilitado, sin renderizar nada cuando no hay nada que decir. La decoración la pone el canon: el elegido reenvía el anillo de Card, el actual su tratamiento deshabilitado. Nada de un data-pricing-selected propio: sería un atributo que nadie lee, porque un block no pinta CSS. Medido de punta a punta en el escenario de cambio de plan: el actual sale con cursor de no permitido, botón deshabilitado y aria-describedby que casa con el id de la frase, traducida por el arnés; al elegir otro, su tarjeta gana el anillo de dos píxeles con el primary de su paleta y las demás no. Queda anotado un gap del canon que salió al medir esto y que no toco: Card disabled nunca atenúa. La regla existe y su cursor sí aplica, pero la animación de montaje fija la opacidad en uno con prioridad de animación y se come la del estado; aislado con no-emerge, atenúa. Es el patrón que la doctrina de motion llama frágil-conocido —dos dueños peleándose por una propiedad en un nodo— y alcanza a toda Card deshabilitada del ecosistema. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
db026e48f5 |
blocks(pricing): la tarjeta responde al puntero, y el guard deja de leer prosa
El plan reenvía el eje nuevo de Card. No es el `interactive` del canon: eso convertiría la tarjeta en un botón y `.PlanAction` ya tiene uno dentro. Apagado por defecto, porque una fila de planes es una comparación y una tarjeta que responde al puntero sugiere que la tarjeta misma es el control; se enciende donde la tarjeta entera ES la elección. Control vivo en la demo y en la URL de la vista previa. Y al escribir el comentario que explica por qué NO se usa un nativo aquí, el guard del tier se puso rojo sobre mi propia prosa: la regla B-2 leía comentarios como código. Su barrido quitaba sólo los de UNA línea, mientras el que entiende todas las formas y conserva los saltos ya existía tres líneas más abajo, usándose sólo para los imports. Es la lección de la fase cinco aplicada a una regla y no a la otra. Arreglado con dos fixtures nuevos —un nativo nombrado dentro de un comentario multilínea es prosa; un nativo de verdad debajo de ese mismo comentario sigue siendo error— y probado en rojo sobre el árbol real: un button inyectado en el hero sale señalado con su línea exacta, y el árbol queda restaurado después. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
8ee90367c8 |
docs(process): el handoff dice donde estamos en la ola
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
a514ef929d |
blocks(cta): el panel gana una segunda columna, y las acciones no se sueltan de su frase
Tercera fila de la ola F2b. El README lo tenía diferido con la razón escrita: recurre en las referencias, es un tercer arreglo y el Mockup del canon ya existe para la media. Entra ahora porque la demo lo pide. Dos decisiones que la ficha del plan no traía y que salieron al colocarlo. La copia conserva sus acciones en la MISMA columna: separarlas dejaría el botón debajo de la captura, lejos de la frase que lo pide, y una llamada a la acción que no toca a su argumento pierde la mitad de su fuerza. Y split sin media cae a justified, no a una rejilla con una pista vacía — la misma regla que ya sigue el split del hero, porque un layout con un hueco reservado miente sobre lo que tiene. La media es del app, como en el hero: el block no nombra cromo propio. La demo compone el Mockup del canon con un panel falso dentro. Medido: a 1280, dos pistas de 436px en el panel de 992; a 375 una sola pista con el CTA POR ENCIMA de la media al apilar, que es el orden del argumento; idéntico en claro y oscuro. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
fd6c55a13b |
blocks(pricing): un plan solo no se sostenía — la fila necesitaba un tope
Segunda fila de la ola F2b, y la que existía para comprobar una sospecha. El README llevaba desde julio diciendo que el plan único «ya lo cubre el layout», diferido por eso. La ficha exigía medirlo antes de darlo por gratis, y no lo cubría. La causa está en la lista de pistas, no en cuántos planes hay: la fila es repeat(auto-fill, minmax(17rem, 1fr)), y auto-fill RESERVA toda pista que quepa, la ocupe alguien o no. Medido a 1280 sobre 992px de fila, un plan solo daba una tarjeta de 315px pegada al borde inicial con 677px de vacío al lado; fijar columns a 1 solo cambiaba eso por una tarjeta de 992px, que es un cartel. Meterlo en un Container sm tampoco: a 640px vuelven a caber dos pistas. Lo que faltaba era un tope sobre la FILA, y la parte no lo dejaba pasar: sus props están cerradas a propósito desde A-94, porque el envoltorio esparce el resto antes de las props que fija y un width del consumidor tipaba y se descartaba en silencio. Así que se abre nombrando: maxWidth entra en el tipo y viaja explícito al AutoGrid, que es exactamente lo que A-94 dejó dicho — el tipo dice lo que la parte honra. El marginX auto va con él sin condición: sin tope la fila ya llena la medida y auto no resuelve a nada. El número lo pone la app. Un block que horneara uno estaría decidiendo cuánto mide un plan; la demo pasa 28rem para uno y 44rem para dos, que es lo que las referencias le dan a cada arreglo, y gana un control vivo de recuento que también viaja en la URL de la vista previa. Medido después: un plan 448px centrado, dos planes 704px en dos pistas de 340 centradas, y tres planes 992px idénticos a antes — el tope es opt-in y no toca el caso que ya existía. A 375 es inerte, porque allí una sola pista ya toma el ancho. Queda anotada la lección en el README y en el handoff, porque no es de este block: una fila «diferida» puede ser una medición equivocada y no un aplazamiento. Nadie había mirado pricing con menos de tres planes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
bedbf4ee14 |
docs(blocks): la variedad se firma antes de construirla — F2b en cuatro tramos
El usuario señaló que el dossier de referencias nos dejaba muy por debajo en variedad. El contraste fila a fila de su §P1 contra los 15 blocks dio 6 filas hechas y 4 resueltas por composición: lo que quedaba pendiente eran DECISIONES de julio, no medidas. Ninguna de las ocho variantes era un hallazgo nuevo — todas estaban ya en el README de su block esperando una firma. Pero ocho variantes no cierran la brecha que se ve, que es de CARDINALIDAD, y el método para esa ya estaba escrito en el dossier (P6.1: demostrar la equivalencia variante-a-variante en vez de competir en número de dumps). La primera versión de la sección no lo trajo y se reescribió entera el mismo día. F2b queda con cuatro tramos: A las variantes de suelo, B la matriz de equivalencia por block —el entregable central, con su plantilla y el guard encendido al final para no dejar 18 READMEs en rojo durante la ola—, C el catálogo que se reabre y D la página compuesta. La regla de forma gana el término que faltaba y que más trabajo hace: una variante alcanzable componiendo es una RECETA demostrada, cero código. Sin él cada fila de las referencias parece pedir un layout nuevo y el tier engorda por imitación. Tres decisiones de catálogo: logo-cloud vuelve (se difirió sobre una premisa que las refs desmienten, y su valor propio es la tinta monocroma derivada de tokens, que nadie más puede dar); blog entra por la misma puerta que team; cookie-consent entra con doctrina propia, porque la ley europea vive en el TIPO — rechazar al mismo nivel que aceptar, no esenciales apagadas por construcción. onboarding NO entra: es un wizard con otro nombre, y duplicar función es la inflación que este modelo existe para evitar. Y el cierre de F2 deja de mentir: cerrada EN UNIDADES. La página compuesta que exigía nunca se construyó, así que el claim de que los blocks ensamblan sin fricción se declara sin demostrar en vez de darse por probado. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
aa60aba6be |
docs(process): el handoff dice donde estamos y por donde se entra
El documento seguia contando la sesion del 2026-08-10: «fase 7, 7 de 15», «quedan 7 en este orden», el ledger a 51/27/12 y unas puertas «SIN COMMITEAR». Nada de eso era cierto ya, y un handoff rancio es peor que ninguno porque se lee como si fuera el estado. Puesto al dia: - **Fase 7 CERRADA 15/15.** Ninguno de los 15 tiene un defecto de composicion: lo que salio vive una capa mas abajo (canon) o en las FICHAS del ledger. Se conserva escrito COMO se llego ahi, porque me equivoque en el camino — declare «conformes» siete blocks de los que solo habia medido un eje, y se corrigio cuando el pregunto si habia terminado. Un barrido no es la rejilla. - **Ledger a 64 ARREGLADO / 19 CONFIRMADO / 13 REFUTADO** sobre 96 filas. - **§Qué queda reescrita como puerta de entrada**, agrupando las 19 vivas por lo que hace falta para cerrarlas en vez de por severidad: tres bloqueadas por decision suya (A-67 al eje de sema con la puerta ya elegida · A-47, que por doctrina es un VALOR de configuracion y no un cambio de CSS por componente · A-09, cuya disposicion apagaria la deteccion live), una bloqueada por una pieza que no existe (A-95 espera a `app-shell`, F3.1), y quince de trabajo normal — con el aviso de que A-55 no es un defecto visible (1,6% del ancho) y de que A-65/A-75 son el mismo patron. - **Las dos sesiones del 16/17** con sus dos commits (`4caf1100a` el renombrado del eje de tamaño, `d02021aee` los dos arreglos de canon), incluido el residuo del 35,4% de A-68, que es peor que mi estimacion y queda escrito como tal. - **Puertas al parar**, con la advertencia de siempre reforzada: la cifra de `svelte-check` SE MUEVE con otras sesiones vivas en el arbol (72, 74, 75, 76, 77 y 80 en dias distintos), asi que se mide justo antes y justo despues del cambio, nunca contra un numero recordado. Y los 8 rojos vivos se nombran uno a uno como ajenos, con el commit que los introduce. Y tres errores de METODO de estas sesiones, que es lo que un handoff existe para que no se repita: 1. Medir un default en una demo que lo pisa no es medir un default. 2. El panel del navegador oculto suspende el IntersectionObserver, no solo el rAF — una medicion dio «contadores congelados» que era la suspension, no el arreglo. La via fiable es Playwright headless. 3. `Motion` es `once: true`: empujar algo bajo el pliegue DESPUES de cargar no lo hace invisible, su observador ya disparo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d02021aee1 |
fix(eidos): dos animadores que no se hablaban y un cluster que no envolvia
Dos hallazgos del contraste doctrinal del tier, ambos de CANON, ambos medidos antes y despues. El tercero (A-67) queda REGISTRADO y sin ejecutar: cae en el eje de sema. ── A-99 · Group no aplicaba NINGUNO de sus dos defaults ────────────────────── Su README los promete en tres sitios («defaults pensados para action rows — `align: center`, `wrap: wrap`»). No pasaba ninguno a `Flex`, que cae a `nowrap` + `stretch`. De ahi que las acciones de `hero` no apilaran a 375px (A-98): 315 de 327px en una linea, con la segunda etiqueta truncandose. ⚠️ CORRECCION A MI PROPIA FICHA. Habia escrito que «el `align` SI se cumple, lo que descarta que el recipe no cargue». Era FALSO: el `center` que medi salia de la demo, que lo pasa explicitamente en su control. `group.css` no declara ni `align-items` ni `flex-wrap` — no habia nada que cargar. Medir un default en una demo que lo pisa no es medir un default. Y de las tres salidas que la ficha proponia, «que lo declare el recipe» es IMPOSIBLE: `flex.svelte` tiene `wrap = 'nowrap'` como default de JS y siempre escribe `--flex-wrap` inline, asi que el fallback de `var(--flex-wrap, nowrap)` no se evalua nunca. Solo ganaria una declaracion dura, que congela el eje. El arreglo usa el mecanismo que el propio fichero ya tenia para `direction` y `effectiveGap`: `align = 'center'` destructurado (sobreescribible — `align` no esta omitido de `FlexProps`) y un `effectiveWrap` interno, `wrap` salvo `attached`, que lo invierte. Espejo exacto de «`attached` implica `gap=0`», y por la misma razon fisica: el recipe cuadra los radios interiores y tira de un margen negativo asumiendo UNA linea, asi que un segmentado envuelto abriria su segunda linea con una esquina plana y el borde recortado. EL CENSO DECIDIO LA FORMA. 77 de 91 usos de `<Group` ya pasan `align`; de los que no, tres blocks lo reenvian con default propio y **`ButtonGroup` es el unico que voltea `Group` a columna en todo el ecosistema** — donde `center` habria dejado los botones verticales en dientes de sierra. Un solo caso no justifica un default condicional, asi que la excepcion vive en su call site. Medido: `hero` a 375x800 pasa a DOS lineas (tops 416,3 / 472,3) y la accion secundaria de 124px truncados a 156,9px, su etiqueta entera. ButtonGroup horizontal `attached` sigue `nowrap`; el vertical, `stretch` con los tres hijos a 301px. El README se corrige entero: negaba `attached` como prop canonico y listaba `grow` como «gap conocido» cuando ambos llevan tiempo implementados — describia la era del baseline `air`. ── A-68 + A-93 · dos observadores que no se hablaban ───────────────────────── `CountUp` trae su propio IntersectionObserver (threshold 0) y `Motion` usa otro (threshold 0.1, rootMargin −10%). Un IO no mira la opacidad, asi que las cifras contaban su recorrido entero detras de `opacity: 0`: por scroll lento la primera aparecia ya al 97,8% de su valor. ⚠️ EL DILEMA QUE PLANTEE ERA FALSO. Dije que escalonar el arranque y aterrizar juntas eran incompatibles — cierto — y de ahi que exponer el `seen` de `Motion` rompiera A-69. No se sigue: el retardo del stagger vive en `animation-delay` (CSS) y los observadores de los cuatro `Motion` disparan en el MISMO instante, porque los stats estan en una fila, a la misma altura. Gatear en el REVELADO da arranque conjunto, duraciones iguales y aterrizaje conjunto intacto. Presente como decision entre dos males algo que tenia una tercera opcion mejor. Tambien descarto la «alternativa minima» que llegue a recomendar (alinear el observador de `CountUp` con el de `Motion`): es acoplamiento por copia — duplica threshold + rootMargin en otro fichero, la deriva que el framework elimina con builders tipados — y cambia los defaults de `CountUp` para todos sus usos sueltos. `Motion` publica ahora su momento visto por contexto (`motion/context.ts`, calcado de `cascade/context.ts`, que ya exponia `open`), y `stats-band-value` lo pasa a `startWhen` ANTES del spread de `countOptions`, para que el app pueda desactivarlo. Cero cambios en `CountUp`, cero observadores propios en el block, B-6 intacto — la costura era justamente lo que faltaba en canon (A-93). Medido con Playwright headless, 1280x800. ⚠️ La primera corrida fue INVALIDA y se descarta: inserto el espaciador DESPUES de cargar, y como `Motion` es `once: true` su observador ya habia disparado con la banda en pantalla. El espaciador tiene que existir en el primer pintado (`addInitScript`). Segunda corrida, con la banda nacida bajo el pliegue: - fuera de pantalla 2,6 s: pending 4, opacidades [0,0,0,0], contadores 0/0/0 - ventana que discrimina (banda a 6px dentro, donde el IO de CountUp dispara y el de Motion no): 0/0/0 en la llegada Y tras 2,6 s. Antes, ahi mismo, corrian (1144/31/4) y acababan practicamente terminados (12.121/330/47) - al revelarse, la primera cifra al 0% — antes 97,8% - A-69 INTACTA: las tres aterrizan a 1781 ms, dispersion 0 ms RESIDUO, y es peor que mi estimacion: las cifras que el stagger revela despues llegan al 16,8% (+70 ms) y 35,4% (+210 ms). Estime «≤10%» y la medicion dice 35,4% — el muelle es sobreamortiguado y cubre mucho recorrido al principio. Es el precio del aterrizaje conjunto que A-69 firmo y no baja sin reabrirla. Queda documentado, no escondido. ── A-67 · registrada, no ejecutada ────────────────────────────────────────── Contrastadas contra `engine.ts` y el canon, las tres puertas de su ficha fallan: el pack no puede distinguir el `collapse` de un intercambio del de un cierre a secas (mismo evento, mismo tipo de nodo; pediria un `:has()` a mano sobre el hermano); el arbitro ya implementa la mitad del «on a tie, the most recent» que un filtro de LLEGADA permite, y la otra mitad exigiria revocar una nota ya agendada, que `EngineSound` no sabe hacer; y un evento unico rompe los `commits`, porque cada uno estampa `data-state` sobre SU item. La puerta correcta no estaba en la lista: un nuance declarado, `emerge-collapse-swap`. La forma es canonica y el provider lo sabe de forma determinista en `setValue`. Cae en el eje de sema, asi que NO se ejecuta aqui — se deja elegida para su sesion. Puertas: svelte-check 72 errores / 59 avisos = linea base exacta · blocks:check 0 sobre 15 blocks · docs:check 0/0 sobre 629 docs. La suite completa deja 8 rojos en 3 ficheros, todos nombrando waveform / skin-media-player / radio-group mas un timeout de orca — ninguno en lo tocado aqui. Prettier solo se queja de ficheros CRLF preexistentes (el diff seria el fichero entero), asi que no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
4caf1100a1 |
refactor(blocks)!: el eje de tamano dice de QUIEN es — size deja de significar dos cosas
LA AMBIGUEDAD, medida. En el canon `size` no es ambiguo: es «el eje de tamano de
ESTE componente», y cada uno lo materializa a su manera — padding de bloque en
`Section`, densidad de la tira en `Banner`, escala tipografica en `Text`. La
ambiguedad la creaba el tier: un block no es ninguno de esos componentes, y al
reenviar `size` estaba reenviando el eje de una pieza INTERNA. Desde fuera:
<FeatureGrid size="xl"> cambia el AIRE de la seccion
<SiteBanner size="lg"> cambia la ALTURA de la tira
Misma prop, dos cosas, y una con un valor menos en la escala (`BannerSize` no
tiene `xl`). Un consumidor que aprende el eje en trece blocks se equivoca en el
catorceavo.
EL RENOMBRADO:
- `size` -> `sectionSize` en los 13 que envuelven `Section`
- `container` -> `containerSize` en los 14 que envuelven `Container`
- `size` queda LIBRE y solo lo usa `banner`, donde si es el tamano del block
- `minColumnWidth` -> `minChildWidth` en `site-footer`: el mismo concepto tenia
dos nombres en el tier, y ninguno era el del canon
El patron no se invento: el tier YA nombraba un eje por su pieza (`container`).
`containerSize` + `sectionSize` lo hace explicito y simetrico.
LA REGLA, en `architecture/blocks.md` §Conventions, para que no reaparezca: un
block que reenvia el eje de tamano de una pieza interna lo nombra `{pieza}Size`,
y `size` se reserva para el tamano del block. Corolario: un eje que el canon ya
nombra se reenvia CON SU NOMBRE.
METODO — se renombraron los TIPOS primero y se dejo que `svelte-check` senalara
cada consumidor, en vez de buscar a mano. Cazo los cinco call sites que quedaban
(`CtaSite`, `HeroSite`, `SiteHeaderSite`, `TeamSite` y un uso suelto en
`cta.svelte`). ⚠️ Y cazo tambien un error propio: el primer patron era demasiado
ancho y renombro `size` en SIETE sub-partes donde ese `size` es el del `Card` o
el `Text` que envuelven (`testimonials-item`, `contact-reason`,
`team-member-role`…). Revertidas antes de seguir.
DOCUMENTACION revisada entera: corregidas las tres filas de README que describian
props del block con el nombre viejo (`hero` x2, `site-header`); conservadas las
seis que hablan del `size` de un componente del canon (`Banner`, `Accordion`,
`Heading`), que no cambia. `PLAN-blocks.md` se deja intacto: es bitacora, y
reescribir el registro historico para que cuadre con el presente lo falsearia.
LEDGER — el contraste doctrinal llega a 15/15, y cinco filas nuevas:
- A-95 `banner`: con `affix="top"` el aviso tapa la cabecera pegada (medido:
`elementFromPoint` sobre el header devuelve la tira). NO se arregla aqui: la
pieza que falta es la que POSEE las alturas de pagina — Mantine lo resuelve en
`AppShell`, que declara `header={{ height }}` y desplaza el resto; nuestro
equivalente es el block `app-shell`, sin construir.
- A-96 `banner`: `affixOffset` es prop publica sin control vivo en la demo.
- A-97 `feature-grid`: RETIRADA el mismo dia. Se midio el texto `muted` con el
4,5:1 generico de WCAG y el proyecto tiene OTRA vara — «floors APCA >= 60 ∧
WCAG >= 3», con guard propio, y la tinta secundaria es «marginal sub-4.5:1 by
design». Re-medido con `src/arts/color/apca.ts`: Lc 63,7 y 3,70:1, cumple las
dos. (El primer intento con `apcaLc` devolvio 102666 porque le pase 0..255
donde pide 0..1: un valor fuera de rango no es un hallazgo, es un formato.)
- A-98 `hero` + A-99 `Group`: las acciones no apilan ni envuelven. La causa NO es
del tier — `Group` no aplica el `wrap` que su README promete en TRES sitios:
`group.svelte` nunca se lo pasa a `Flex`, el recipe no lo declara, y
`Omit<FlexProps,'wrap'>` impide compensarlo desde fuera. El `align: center` si
se cumple, lo que descarta que el recipe no cargue.
Y A-25 cae (la demo ya no promete elevacion) y A-47 corrige su disposicion: el
anillo de foco es «a config axis» por doctrina — endurecerlo es una decision de
VALOR en `color.focus.ring`, «never a per-component CSS change», que es
exactamente lo que hacia el intento revertido en `e468e764b`.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 72 errores /
59 avisos = linea base exacta, ninguno en blocks · docs:check 0/0 sobre 629 docs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
2447a466b4 |
docs(process): el handoff dice donde quedo publicado
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
65536ae6c8 |
test(eidos): el enum que salio del morfo vuelve, leido de la union de TypeScript
Los 25 knobs visuales salieron del contrato en
|
2 months ago |
|
|
948f7cf7e1 |
feat(blocks): el guard vigila la frontera, y siete filas del ledger caen medidas
FASE 5 DEL SANEAMIENTO — `blocks:check` deja de vigilar solo la forma del tier
y pasa a vigilar su frontera, su documentacion y su catalogo. Tres reglas, cada
una con su fixture negativo en el selfTest():
1. Lista blanca de importaciones (B-4). La frontera dura 1 era prosa; ahora es
codigo: $uix, los arts publicos, $libs/forms, svelte y los relativos del
propio block. Los arts se DERIVAN de src/arts/*, no se listan a mano — una
lista escrita se queda atras el dia que aterriza un art. El tier ya cumplia:
cero violaciones al encenderla.
2. README completo (B-9) + declaracion de landmark (B-8). Salieron 7 de 15 sin
ella; escritas leyendo la fuente de cada block, no la plantilla.
3. Ficha en el catalogo de demos (B-9), en los DOS sentidos: un block que el
catalogo no publica es invisible para el rail, y un slug publicado sin block
detras es un enlace muerto.
De paso el escaner deja de leer los comentarios como codigo: los index.ts
documentan su uso con un `// import { Cta } from '$blocks/cta'`, y una lista
blanca que lee prosa habria empezado a acusar a los ejemplos.
Verificado EN ROJO sobre el arbol real, no solo contra los fixtures: un zod y un
$libs/days metidos en hero/types.ts salen con su linea exacta mientras el mismo
import dentro de un comentario NO salta; el catalogo falla en las dos
direcciones; y al romper isAllowedSpec a proposito el self-test aborta el guard
en vez de dar verde.
ARREGLOS DEL CONTRASTE DOCTRINAL — siete filas, cada una con medicion o gate:
- A-88 team: `height="100%"` en el Stack del Member. El div que pinta Motion es
un bloque pelado, asi que el Stack se quedaba a altura de contenido y el
`margin-top:auto` de los enlaces repartia CERO. Medido: la desviacion entre
filas de iconos pasa de 20px a 0, y el margen reparte 20,297px.
- A-94/A-28 (feature-grid, team, testimonials): los tres declaraban su tipo como
`= AutoGridProps` y ponian {...rest} ANTES de las props que fijan, asi que un
consumidor podia escribir align="start" y no obtener nada. Cerrados con
Pick<AutoGridProps, ...>, que conserva los tipos exactos del canon.
- A-84 feature-grid: el eje `align` llega a las partes por contexto, como en
team, en vez de que el typedoc instruya al app a repetirlo. Medido: con
align=center el align-items computado es center en cabecera Y celdas.
- A-19 faq: `marginX="auto"` en el Header — Box no centra solo. Medido: el Box
de 768px resuelve margin-inline 248px/248px donde antes daba 0.
- A-27 testimonials y A-86 pricing: el typedoc decia `soft` donde el codigo hace
`outline`, y el ejemplo de la puerta de entrada no compilaba.
- A-91 banner (fila nueva): el block hereda el eje intent/color que el canon
declara fuera del sistema abierto en su propio typedoc, sin registrarlo.
LEDGER — dos lecciones de instrumento escritas dentro, porque ambas produjeron
hallazgos falsos publicados:
1. El dev server sirve codigo ANTERIOR a HEAD si reusa cache de Vite. Tres filas
(A-36, A-69, A-85) se dieron por vivas estando arregladas. El delator fue
aritmetico: las duraciones medidas eran EXACTAMENTE las que el comentario del
arreglo cita como estado anterior.
2. Contar nodos WebAudio no es medir sonido: la sintesis crea dos osciladores
por earcon, un sound pack de muestras cambia la aritmetica entera, y crear
nodos no es sonar. El contador responde "hubo actividad" y nada mas.
Y el error de metodo del que ambas son sintoma, tambien registrado: se fue a
re-medir A-67 sin leer su ficha, que ya traia los gains del catalogo, el empate
de applyDominance y la regla de CANON.md incumplida — mejor razonado que la nota
que lo sustituia.
Gates: blocks:check verde (15 blocks / 115 ficheros) · svelte-check 69 errores /
57 avisos = linea base exacta, ninguno en lo tocado · docs:check 0/0 · prettier
limpio en todo lo del commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b72a564918 |
fix(eidos): la capa sale de components/ — tenerla ahi acoplaba Fab y MenuDial a Affix
Defecto de diseno mio, cortado por el autor. Mientras la capa vivio en
`components/affix/affix.css`, sus consumidores la importaban con
`'../affix/affix.css'`: **un componente dependiendo del directorio de otro**, que
es canon prohibido — «un componente no es libreria de otro». `Fab` y `MenuDial`
quedaban esclavizados a `Affix` por una ruta, cuando lo unico que comparten es
geometria.
Va a `eidos/lib/viewport-placement.css`, junto a `lib/list-surface.css`, y los
tres consumidores importan de ahi. Ninguno depende de ningun componente.
LO QUE HIZO FALTA PARA PODER MOVERLA, que es lo que me faltaba entender. Ya lo
intente esta manana y lo revirti porque `recipe-css-contract` fallaba — deje que
el guard dictara la arquitectura en vez de preguntarme por que `list-surface` si
puede vivir en `lib/`. La respuesta era el diseno entero: **no tiene clave de
receta**. Declara sus `--list-*` dentro de su propio CSS, componiendo primitivas
que ya son temeables.
Aplicado igual: la capa declara `--viewport-placement-offset` / `-z` sobre el
propio gancho, compuestos de `var(--space-4)` y `var(--z-index-affix)`. Sin clave
en `recipes/base.ts` no hay exigencia de `components/{c}/{c}.css`, y sin esa
exigencia no hay acoplamiento. El retoque ademas queda a la altura correcta: un
tema mueve la escala de espacio y la escalera de z, no un alias por componente.
Renombres que arrastra, todos hacia nombres de CAPA y ninguno hacia un
componente: `--affix-offset`/`-z` -> `--viewport-placement-offset`/`-z`, sus
ranuras `--_affix-*` -> `--_viewport-placement-*`, y las dos customs del remapeo
de safe-area. La clave `affix` sale de `recipes/base.ts` y sus dos tokens del
`:root` generado.
Sin cambio de comportamiento, medido en las tres rutas: `Fab` sigue en `fixed`,
z 100 (la capa ofrece 150 y su puente la baja por la ranura — los dos niveles
haciendo su trabajo) y a 16px de sus dos bordes; `Affix` en sus nueve zonas
exactas con z 150; y su elemento lleva solo `data-affix`, la identidad.
check 69 = base intacta · component:audit affix 0/0 · fab 0/2 · menu-dial 0/0 ·
layer:check 0/3 · suite eidos 123/123 · rtl 0/178 · docs:check 0/627 ·
smoke 311/311.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
de0615caf2 |
fix(morfo): affix deja de declarar lo que solo lee una capa, y el gancho pasa a nombre de CAPA
Correccion doctrinal, senalada por el autor: **morfo es la capa declarativa ENTRE
capas**. Un atributo que consume una sola no es contrato.
`affixMorfo` declaraba `data-affix-placement` y `data-affix-stretch`. Los lee
UNA: el CSS. No debian estar ahi — y la doctrina nombra la familia exacta
(`morfo.md` §`undeclaredState`: «the escape hatch for attrs OUTSIDE the contract
— a visual wrapper's `data-size`, a presentation flag like `data-sheet`»). Los
declare porque `morfo-check` los exigia, que es la herramienta dictando la
doctrina: el guard clasifica por PREFIJO DE NOMBRE, la doctrina clasifica por
NATURALEZA, y las dos solo chocaban porque el gancho llevaba el nombre del
componente.
Y no era su nombre. Lo estampan tres —`Affix`, `Fab`, `MenuDial`—, asi que no es
de ninguno: pasa a **`data-viewport-placement`** / `data-viewport-stretch`. La
colision con el guard desaparece por construccion, sin excepcion que escribir.
Es el mismo error que `--fab-offset` leido por la capa: nombrar por un
participante algo que es de todos.
`affixMorfo` se queda con lo que si es contrato: la identidad `data-affix`, que
es como el DOM dice «esta caja es un Affix» a quien pregunte.
TAMBIEN INTENTADO Y REVERTIDO, con su motivo, para que nadie lo reintente:
mover el fichero a `eidos/lib/viewport-placement.css` junto a `list-surface.css`.
`recipe-css-contract` lo tumbo y tenia razon — **el sistema de tokens esta
indexado por componente**: toda clave de `recipes/base.ts` exige su
`components/{c}/{c}.css`. `list-surface` puede vivir en `lib/` porque NO tiene
clave de receta (sus `--list-*` viven dentro de su propio CSS, por `data-size`,
no como defaults temeables en `:root`); los nuestros si lo son. La parte portante
—«esto no es de nadie»— la lleva el nombre del gancho, no la carpeta. Queda
escrito en la excepcion E-2.2 del README y en el PLAN.
Sin cambio de comportamiento: 68 sustituciones de nombre, mismas reglas, mismos
valores. Verificado en las tres rutas.
check 69 = base intacta · component:audit affix 0/0 · fab 0/2 · menu-dial 0/0 ·
morfo:check affix PASS · layer:check 0/3 · contrato de capa 13/13 ·
recipe-css-contract + api-contract 50/50 · rtl 0/178 · smoke 311/311.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
a11805d434 |
docs(eidos): la doctrina alcanza a la jornada — dos guards nuevos, el patron de capa, y EID-3 deja de estar pendiente
Barrido de lo que la sesion cambio y la documentacion todavia no decia. `testing-and-tooling.md` — los dos guards nuevos entran en la tabla de «que atrapa cada script», con su reparto explicito: `layer:check` mira el valor computado (quien gana la cascada) y declara su hueco (la geometria, porque `getComputedStyle` da el valor USADO y un `inset: auto` se lee como pixeles); `shared-layer-contract.test.ts` mira el texto, y existe por lo que el navegador no puede ver — un `env()` ya sustituido devuelve `"0px"` en escritorio. `component-guide.md` fila RTL — deja de remitir a «EID-3 exception» y enuncia la regla: dos rejillas NOMBRADAS, `Position` fisica y `LogicalPosition` logica, y la pregunta que decide entre ellas («¿tiene que voltearse para un lector de derecha a izquierda?»). Estrechar con `Extract<>`, nunca redeclarar. `canon/recipe-contract.md` — la fila de z-index flotante distinguia mal: la banda `--z-index-overlay-*` es de overlays PORTALED. El cromo fijado al viewport que no portala es otra cosa y tiene su peldano (`affix`, 150). Y el item 9 del checklist de recetas decia «si flota → una rung de overlay», que era incompleto. `eidos/components/README.md` — el patron de CAPA COMPARTIDA, que no estaba escrito en ningun sitio pese a tener dos ejemplares vivos (`list-surface` y `affix`). Sus cuatro reglas, tres de ellas aprendidas rompiendose: enganchar en el attr de capa y no en la identidad (o `morfo-check` suelda al consumidor a un contrato ajeno), un eje = token publico + ranura, el puente reafirma `position` si el primitivo compuesto declara uno, y se guarda con dos redes porque ninguna basta sola. `audit-active-uix.md` — EID-3 pasa a RESUELTO, conservando el hallazgo original debajo. Era un P3 de julio cerrado «como excepcion» pero marcado en su propia tabla resumen como «pendiente de doctrina explicita». Ya no lo esta. `PLAN-affix.md` §7 — lo que vino DESPUES de cerrar el plan, que es casi todo lo interesante: las dos migraciones y sus dos lecciones, el peldano de z, el patron de tokens, la canonizacion de la rejilla y los dos guards. Ninguna estaba prevista en el plan; todas salieron de auditar lo construido. docs:check 0/627. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
92c8f6466a |
feat(eidos): Affix — la pieza que Sticky no puede ser, y la capa que Fab y MenuDial ya habian copiado dos veces
Un aviso al principio del documento se va con la pagina: `position: sticky` se despega en cuanto termina su contenedor. Medido el 2026-08-10 sobre el block `banner` — `Sticky edge="bottom"` acaba en `top: -8` sin `data-stuck`. No es un cableado roto: es que un elemento anclado al viewport es `fixed`, y eso es otro componente. Pero no es un componente nuevo: la capacidad YA existia dos veces. `fab.css` fija 4 esquinas, `menu-dial.css` nueve, y las dos ya divergian (`--fab-offset` publico vs `--_menu-dial-offset` interno; `var(--fab-z)` vs `var(--z-index-sticky, 1100)`, un fallback de 1100 sobre un token que vale 100). Y antes de eso existio entera: `air/layout/float`, el primitivo de 9 zonas que el refactor retiro sin sustituto — lo dicen los README de `float` y de `Fab`. Asi que `affix.css` no es un tercer ejemplar: es la CAPA, enganchada en `data-affix-placement` y no en `data-affix`. El split es portante — `morfo-check` selecciona `[data-affix]` en toda la pagina y valida cada match contra `affixMorfo`, asi que si `Fab` estampara la identidad quedaria soldado a este contrato. Estampando solo el gancho de capa, no. Es el patron de `lib/list-surface.css`: una fuente, cero nodos extra. Lo que salio midiendo, no razonando: - `--z-index-affix: 150`, peldano nuevo. Con la tira en `top` empataba con `--sticky-z-index` (100 los dos) y el empate lo rompe el ORDEN DEL DOM: el aviso va antes que la cabecera en el fuente, luego perdia. 30,7px de solape con el cromo encima — el fallo original reproducido por su sustituto. Un peldano toca DOS sitios: `STATIC_Z_INDEX` y la lista cerrada `Z_INDEX_KEYS`. - NO compone `<Box>`, aunque sea el patron de los primitivos de layout: `box.css` declara `position: var(--box-position, revert-layer)` a la misma especificidad que el gancho, y eidos no usa `@layer`. En el orden de carga equivocado, `position: static` — un `fixed` que no hace nada. - `stretch` es solo de los bordes de bloque. El rail del eje inline se envio y se retiro el mismo dia: `Affix` no dimensiona a su hijo, asi que era una caja invisible de 380px con el hijo de 47px arriba, identica a `top-start` en pantalla. El marco medido decia otra cosa; la pintura mandaba. - `env(safe-area-inset-*)` es fisico y los anclajes logicos: remapeado bajo `:dir(rtl)`. Emparejar `inset-inline-start` con `safe-area-inset-left` despeja la muesca equivocada en RTL apaisado — que es lo que hacen hoy `fab` y `menu-dial`, registrado. `banner` recupera lo que su README daba por imposible: `affix="top" | "bottom"`. La disposicion anterior decia «app-land, el canon ya trae Sticky» y nombraba un componente que no puede hacerlo. Verificado con recorrido real (342px en la demo, 1247px en la preview del block): desplazamiento 0 en los dos bordes, nueve zonas exactas, RTL espejando, el ancestro transformado derivando -342px como esta documentado, y `elementFromPoint` sobre la tira devolviendo el aviso y no el cromo. component:audit PASS 0/0 · morfo:check PASS · rtl 0/178 · smoke 311/311. Queda: migrar `Fab` y `MenuDial` a la capa. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
e468e764b2 |
revert(eidos): A-47 — el rescope del anillo de foco se deshace, y queda el registro de los errores
Por orden del autor. Mi arreglo del 2026-08-13 (
|
2 months ago |
|
|
89e42735e6 |
fix(eidos): A-69 — `duration` de count-up dice la verdad, y las cifras aterrizan juntas
`duration` se documentaba como «approximate count duration in seconds» y alimentaba los parametros del muelle con λ FIJA: el asentamiento real crecia con el logaritmo de la magnitud. Medido por el ledger y reproducido: 12.500 tardaba 7,88 s (3,9x lo prometido), 340 → 5,09 s, 48 → 3,57 s, 4,31 s de dispersion — y la razon 2,21 entre cifras era INVARIANTE con duration, asi que ningun valor global las igualaba. `onEnd` disparaba desde un timer ciego a `delay + duration`, 5,9 s antes de que la cifra grande parase. La ejecucion es la disposicion del ledger, tras el re-analisis que pidio el autor — mi primera propuesta sustituia el muelle por un ease-out temporal, que era cambiar el DISEÑO de tapadillo: la caida exponencial (arranque rapido, aterrizaje suave) es la semilla que el componente porta, y mi cita de motion.md §drivers para descartarla estaba fuera de jurisdiccion (gobierna presets de UI sobre propiedades CSS; esto es numero→formatter→textContent). La forma se queda; el RELOJ se recalibra: - λ derivada de (recorrido, umbral, duration): el asentamiento es analitico (|y(t)| ≈ AMP·|d0|·e^(−λt)), asi que λ = ln(AMP·|d0|/umbral)/duration hace aterrizar la ULTIMA cifra mostrable exactamente en `duration`, para cualquier magnitud — y una banda de contadores aterriza JUNTA por construccion. ζ fija en 2√2 (el caracter del default de la semilla). - `onEnd` desde la parada REAL del muelle (el callback de settle), no del timer; el tope de 30 s se queda; recorrido menor que el umbral aterriza instantaneo; reduced-motion intacto (su rama es previa y no se toca). - types.ts y README dejan de mentir: «asienta en ≈ duration sea cual sea la magnitud». MEDIDO con Playwright headless (sonda espejo de probe-A-69b del ledger: muestreo DIRECTO de textContent cada 40 ms — su propia ficha documenta el sesgo del MutationObserver), sobre la demo real de stats-band: ANTES 12.500 → 7867 ms · 340 → 5083 ms · 48 → 3567 ms (4310 de dispersion) DESPUES 12.500 → 1977 ms · 340 → 1977 ms · 48 → 1977 ms (dispersion 0) con los valores finales exactos y su agrupacion de locale. La fila del ledger pasa a ARREGLADO sin commitear el fichero (WIP de la otra sesion, protocolo A-85). NO tocados, declarado: A-68 (arranques vs entrada escalonada — item propio) y el driver `spring()` de $motion (sustrato distinto; la nota queda: si un contador necesita fisica interactiva algun dia, el driver existe). Con esto §3.6 (blocks) queda COMPLETO: A-47 y A-69 cerrados. Verificado: eidos 393 ✓ · check 69 = base aislada, diff VACIO · prettier: types.ts limpio en HEAD queda limpio; count-up.svelte y README ya fallaban. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
acda70f1e4 |
fix(eidos): A-47 — el anillo de foco sostiene el contraste sobre CUALQUIER superficie solida
El anillo global es primario translucido — suave a proposito — y sobre un `Surface variant="solid"` de color primario compone primario-sobre-primario: medido en la demo real del cta, 1,02:1 (peor aun que el 1,43:1 del ledger; misma pareja, panel rgb(142,78,198)). Los DOS unicos elementos interactivos del block quedaban sin indicador, contra el minimo 3:1 de WCAG 1.4.11. Arreglada la CLASE, no la instancia: cualquier hijo enfocable de cualquier superficie solida, en cualquier color y modo. Una declaracion en la regla solida que ya existia en surface.css rescopa `--focus-ring-color` a la misma tinta que el texto de ese lienzo — el slot `contrast(on-solid)`, elegido por APCA con suelo WCAG2 (rfc-color-engine §8). Como §32 canonizo UN solo modelo de foco (outline, todos los consumidores beben del var: button, calendar, breadcrumb, anchor-nav…), la variable rescopada repara el catalogo entero por cascada; forced-colors conserva su camino `Highlight`, ajeno a los tokens. A plena fuerza y sin `color-mix` — aritmetica sobre un token garantizado re-pierde la garantia (la clase «el silencio es un valor»). Medido antes/despues con sonda de composicion (canvas getImageData — el panel pinta en oklch wide-gamut y el ring en color(srgb …/0.52); un parser rgb da null) y sobre el ELEMENTO real: dentro del panel 1,02:1 → 5,18:1 (outlineColor del boton enfocado) fuera del panel 2,17:1 → 2,17:1 (el token global INTACTO) Dos hallazgos anotados sin decidir: `--focus-ring-color-error` sobre solido (volcarlo a la tinta de contraste borraria la semantica de error) y el 2,17:1 del anillo global contra la PAGINA (el indicador puede satisfacer 1.4.11 contra el boton adyacente; abrirlo es decision de diseño). La fila del ledger pasa a ARREGLADO pero el fichero NO viaja en este commit: es WIP de la otra sesion (protocolo A-85). Verificado: eidos 393 ✓ · check 69 = base aislada, diff VACIO · prettier: surface.css estaba limpio en HEAD y queda limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
23b20fff5c |
feat(active-uix,sema)!: S-19(ii) — announce encendido por defecto: un sumidero, materializado por la raiz
¿Debia encenderse? El analisis dijo que era LA RAIZ O NADIE: la opcion
`announce` se pasa al construir el engine dentro de `createActiveUix(options)`,
y en ese momento `uix.announce` no existe todavia — huevo y gallina. El unico
cableado que un app podia escribir (`announce: { dom }`) caia en las regiones
propias del canal: un SEGUNDO par de regiones vivas, exactamente lo que la
doctrina «ONE sink» (AUX-1) prohibe. La unica puerta alcanzable violaba la
doctrina; la raiz es el unico sitio que sostiene las dos puntas.
El impl registra el canal POST-construccion con cierre tardio —
`events.register(new AnnounceChannel({ announce: (m, p) => this.announce(m, p) }))`
— la misma forma para standalone y attach. Default ON, y la asimetria con
sound/haptic (apagados) es doctrina, no inconsistencia: esos son ORNAMENTO
(opt-in); announce es SUSTITUCION (sema.md §channels — «the one that
substitutes»), la categoria que el framework ya enciende solo (las regiones de
uix.announce se crean solas, el camino de soma esta siempre activo). La norma
del campo hace lo mismo: Angular CDK LiveAnnouncer es singleton por defecto,
React Aria usa una region ambiental de modulo. Un framework de referencia no
hace opt-in la accesibilidad.
Opt-outs estandar: `events: { announce: false }` — el engine gana el flag
legible `announceOptedOut` (las opciones de construccion no eran observables) —
y un `announce` explicito del app gana: la raiz jamas pisa un canal existente.
Cero doble anuncio, por diseño ya verificado: el runtime de soma no mete
`message` en la señal perceptual (su a11y viaja por sources.announce), asi que
el canal no-opea para todos los componentes de soma.
Visto en ROJO via stash del impl (canal sin registrar) y en verde con el:
una señal con `message` por `uix.events` aterriza en la region COMPARTIDA
(`uix-announce-assertive`, prioridad derivada del intent threat) y el
documento tiene UN solo [role='alert'] — el par fantasma nunca nace. El
opt-out respetado. `sema.md` §announce reescrito: el ⚠️ de «ninguna raiz lo
cablea» pasa a documentar el default y sus salidas.
Verificado: sema+morfo+eidos+contracts 947 ✓ / 6 ajenos · soma navegador
1278✓/1 (el timeout ajeno — TODOS los tests de navegador arrancan la raiz
tocada) · check 69 = base aislada, diff VACIO · docs 0/624 · prettier:
engine.ts limpio en HEAD queda limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
3ee17f333c |
docs(sema): los dos hallazgos abiertos quedan DECIDIDOS y escritos donde viven
El autor resolvio los dos hallazgos que salieron al cerrar §3.4/§3.5, y su
decision se documenta EN EL CODIGO, no en un handoff que nadie relee:
1. La rama de politica de `validateSemaEvent` es inalcanzable — SE QUEDA.
`isSemaEvent` es policy-aware (rechaza required-sin-intent y, desde S-33,
forbidden-con-intent), asi que una violacion sale por el throw generico y
el mensaje especifico nunca dispara. MEDIDO, no deducido:
`validateSemaEvent({family:'commit'})` lanza «is not a valid canonical
semantic event». Se conserva porque los dos guards responden preguntas
distintas —predicado de tipo vs validador de politica— y solo su ORDEN hace
redundante a uno; reordenar para hacerla alcanzable cambiaria el mensaje
lanzado sin que nadie lo pida, y borrarla sacaria el enunciado de la
politica de la funcion que lo posee. Si el predicado deja de imponer
politica, la rama ya esta aqui y correcta.
2. `SemaActionEvent` y sus tres funciones no tienen consumidor en produccion —
SE GANAN EL SITIO. No es falta de consumidor: es AUDIENCIA. Es la
superficie publica que usa una app conduciendo `EngineSemantic` SIN soma
para nombrar una ocurrencia — el mismo publico que sirve el canal announce
(dos emisores, dos publicos, §announce de sema.md). Soma nunca toma ese
camino porque baja la declaracion estructurada del morfo hasta abajo; la
asimetria es el diseño. Y por eso se mantiene ATADA al contrato: S-35
estrecho la clave para que la forma etiqueta no regale lo que la
estructurada rechaza, con los tests de politica vigilandolo.
Verificado ademas, a peticion del autor: el cableado del canal announce en las
raices NO estaba hecho — `define-engine-semantic.ts` no menciona `announce` y
`active-uix` solo tiene su propio sumidero. Lo que S-19 cerro fue el
desajuste de firmas que hacia IMPOSIBLE escribirlo; encenderlo sigue abierto
como decision, y queda anotado en el handoff con esa distincion.
Verificado: sema+morfo+contracts 552 ✓ / 6 ajenos · check 69 = base aislada,
diff VACIO · docs 0/624 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
f8dd8ff810 |
fix(morfo,sema)!: M1(ii) gradient-picker — el reset delegado deja de declararse dos veces (S-14)
El morfo declaraba `commit-reset`, el pack le dedicaba una regla (settle + tick) y los dos README lo tableaban — y el provider no lo emitia. Lo unico que suena en el Clear es el `commit-reset` del Picker generico COMPUESTO, con la firma del pack `picker`. La ficha S-14 ofrecia dos salidas y su premisa comparativa era falsa, medido: los hermanos color/date/time-picker SI emiten su propio reset (date-picker cableado el 2026-08-11) — pero NO componen el Picker generico: definen su propio pickerShellHandle y son dueños de su transaccion. Gradient-picker la DELEGA entera (su docblock: «The transaction — open / commit / cancel / clear — lives in the composed generic PickerProvider»), asi que la emision sigue a la propiedad: el reset es del picker. Cablear el gemelo habria dado DOBLE firma por un gesto (dos estampados + dos sonidos casi simultaneos — la clase que file-upload pago), y PickerProvider no ofrece hook para silenciar el suyo. Retirada completa, con la palabra explicita del autor: el evento fuera del morfo (una nota en su lugar dice por que y hacia donde), la regla muerta fuera del pack, las filas fuera de los dos README (el de eidos gana la frase correcta: los eventos propios son los del DOMINIO — presets — y el Clear es del picker), la excepcion fuera del censo y el ultimo id fuera de la deuda D9. Con esto la parte (ii) de M1 esta COMPLETA — las 7 resoluciones de la cola medida el 2026-08-12: tooltip x3 CABLEADOS (receta F4 + silencio visual minimo medido), virtual-list/grid x3 `emission: 'host'` (la decision escrita de sus providers, ahora expresable), gradient-picker RETIRADO (delegacion). INERT_EVENT_DEBT y EMISSION_EXCEPTIONS quedan VACIAS por primera vez, cada una con su lapida narrando las resoluciones. M1 entero cerrado: el contrato dice quien dispara cada evento, y ningun evento declarado miente. Sin cambio de conducta: el Clear sonaba por el picker y sigue sonando igual. Verificado: guards 339 ✓ / 6 ajenos · check 69 = base aislada, diff VACIO · docs 0/624 · prettier: los dos ficheros limpios en HEAD quedan limpios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c73cae0227 |
feat(morfo,soma): M1(ii) virtual-list/grid — el scroll declara su emisor: el host
Los tres `handle-scroll*` de virtual-list/grid estaban en la deuda del D9 como «declarados y nunca sonados». Mal clasificados: la resolucion YA estaba escrita en los dos providers como decision con su porque — el listener de scroll dispara por pixel y la familia handle es ['sound','haptic'] (trinquete `step` + tick), asi que una emision por defecto seria un clic-clic continuo bajo scroll con inercia; «apps that want scroll-driven sema feedback should wire it themselves with their own throttle / debounce policy». Lo que faltaba no era el emisor: era un sitio DECLARABLE para esa decision. Es exactamente el caso de diseño del valor `host` de D.2, estrenado aqui: el contrato declara la superficie, el emisor es la aplicacion anfitriona. - `emission: 'host'` en los tres eventos, con el porque en el docblock. - Los dos comentarios de provider apuntan al flag y corrigen la cita desfasada (`activeChannels=['haptic']` — hoy son dos canales, lo que hace la conclusion MAS cierta, no menos). - La promesa del host es viable, verificado: `provider.runtime` es campo publico — un app puede emitir con su propia politica. - Deuda D9: -3 ids. Queda UNA entrada en INERT_EVENT_DEBT: gradient-picker commit-reset (S-14), la ultima resolucion de la parte (ii). Sin cambio de conducta: cero emisiones nuevas, cero navegador que medir. Verificado: guards 309 ✓ / 6 ajenos · check 69 = base aislada, diff VACIO · prettier: virtual-list.ts limpio en HEAD queda limpio; los otros 3 ya fallaban. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
daa6461d62 |
feat(soma,morfo,eidos): M1(ii) tooltip — los tres eventos existen por fin, y el silencio queda donde estaba firmado
El morfo declaraba emerge-present / emerge-dismiss / emerge-dismiss-escape y
nadie los emitia: el SILENT del pack nunca casaba, la puerta de temas que su
propio docblock documenta no podia funcionar, y el preset present-rise que
declara `event: ['emerge-present',...]` jamas pintaba. La excepcion del censo
lo decia desde agosto: «Today's silence is accidental, not the declared
design».
El re-analisis pedido por el autor cazo lo que la primera propuesta no vio:
la firma present-rise es GLOBAL (base.css:6520), la entrada por data-state es
el workaround de la emision ausente (el comentario del provider lo dice
literal), y la semantica autorizada distingue hover (entrada) de focus
(instant-open, «appears with no entrance — its semantics») — cosa que el
evento no distingue. De ahi la opcion B firmada: el estado conserva la
entrada; la emision añade la superficie que faltaba.
Cableado con la receta F4: `present` pre→post — y SIN su `commits` (fijaba
'delayed-open' y pisaria el 'instant-open' del camino focus; los overlays
binarios conservan el suyo porque su valor es total, desviacion declarada);
dismiss ×2 ganan `targetFallback: [trigger]` (Presence sostiene el content por
la salida; el fallback cubre la carrera). Provider: emision SOLO en
transiciones reales — un open-timer cancelado no presenta nada que retirar, un
re-hover abierto no re-emite — y `handleClose` lleva la causa para distinguir
el Escape.
MEDIDO en navegador, los tres caminos y la fuga exacta:
hover present CONTENT · entrada autorizada corriendo (scale-in+fade-in),
present-rise NO — el preset gana la cascada; miedo al doble
movimiento REFUTADO en el camino delayed
focus present CONTENT con instant-open y present-rise CORRIENDO — la fuga
predicha, confirmada → silencio visual MINIMO en la receta, scoped a
[data-state='instant-open'][data-event-phase='active'];
re-medido: anims [] y la entrada delayed intacta
leave dismiss CONTENT via Presence · salida autorizada (scale-out+
fade-out), dismiss-fade no
Escape dismiss-escape CONTENT · desestampado del hold a ~240ms
Deudas limpiadas en el mismo commit (el guard lo exige): 3 ids de
INERT_EVENT_DEBT + 3 excepciones de EMISSION_EXCEPTIONS. Quedan 4 de la parte
(ii): virtual-list/grid ×3 y gradient-picker commit-reset (S-14).
Verificado: soma navegador 1278✓/1 (el timeout ajeno) · tooltip 4/4 ·
sema+morfo+contracts 552✓/6 ajenos · check 69 = base aislada, diff VACIO ·
prettier: morfo/tooltip.ts limpio en HEAD queda limpio; los 3 avisos ya
fallaban en HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
1c4303b10f |
feat(morfo,sema,contracts): M1(i) — el contrato dice QUIEN dispara cada evento (D.2)
El morfo no podia decir quien emite un evento declarado: todo se presumia del runtime, y la verdad de las excepciones vivia fuera del contrato — en la lista de deuda del guard D9 y en las excepciones del censo de packs. Las tablas de eventos de los README prometian percepcion que no ocurre (la ficha M1, P1). Ejecuta la decision firmada D.2 (IMPLEMENTATION_CONTRACT): - `emission: 'runtime' | 'host' | 'external' | 'declared-only'` en MorfoEvent + literal en el schema sium. Ausente = 'runtime': las ~250 declaraciones existentes no se tocan y la presuncion sigue siendo la norma. - El guard D9 exime por DECLARACION en vez de por lista: solo los 'runtime' exigen emisor. Y si un evento eximido sigue en INERT_EVENT_DEBT, el guard FALLA con «FIXED — remove it», para que la deuda no sobreviva a su resolucion. - El pack-census gana el chequeo simetrico: una regla cuyo alcance son SOLO eventos 'declared-only' afina una percepcion que jamas estampara — muerta por definicion, sin lista de excepciones. 'host' NO cuenta como muerto: el anfitrion dispara por el runtime y la regla casa normal. Fixture con los dos casos (positivo y negativo con hermano runtime al alcance). - `morfo.md` §Step 5.5 lleva el apendice tecnico en los terminos que D.2 exige: la tabla de los cuatro valores × sus guards, y la nota doctrinal de por que NO es doctrina del libro — marcar un evento inerte como declared-only para callar al guard es ensanchar la deuda con otro nombre. La sonda temporal (flag sobre tooltip.emerge-present → correr → revertir) destapo un agujero real y lo cerro: el flag entre `name` y `semantic` hacia INVISIBLE el evento al regex del guard — ni censado ni exento. El regex tolera ahora la linea opcional, y la sonda termino dando la conducta disenada exacta: exencion + exigencia de limpiar la deuda. NADIE estrena el flag: los 7 de INERT_EVENT_DEBT quedan intactos y son la parte (ii) — siete decisiones perceptuales del autor, una a una (la de tooltip: ¿el componente mas ubicuo merece firma, o silencio declarado?). La instancia original de la ficha (los 5 pickers con close inerte) esta muerta desde la normalizacion de nombres. Verificado: suites 945 ✓ / 6 ajenos · docs 0/624 · check 69 = base aislada, diff VACIO · prettier: mis dos limpios siguen limpios, pack-census formateado (regresion mia), contracts y morfo.md ya fallaban en HEAD. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
846d201693 |
fix(eidos): E2+E3 — el modo de sistema pasa por ActiveDom y un re-derive fallido ya no desviste la pagina
E2. `createSystemColorSchemeSource` usaba `globalThis.matchMedia` +
`addEventListener` crudos: la regla de la casa (listeners de window/document
pasan por ActiveDom) y la ventana EQUIVOCADA en iframe/popup — globalThis es la
global, no la del dom. Ahora, con dom inyectado, el media query sale de
`dom.getWindow().matchMedia` y la suscripcion va por `dom.listen` (ciclo de
vida gestionado); sin dom, el camino crudo queda de fallback (serializacion
CSS / SSR). Descartada la opcion preferida de la ficha (tracker en adom,
espejo de prefers-reduced-motion) por la regla de ≥2 consumidores: leidos los
6 boots reales de web/, TODOS pasan su propio modeSource — el camino de
sistema tiene hoy cero consumidores vivos; si algun dia gana un segundo, se
promociona, y queda dicho en el comentario.
E3. `#renderSchemeCss` atrapaba el error del re-derive y devolvia '' — y
`apply()` lee '' como «sin esquema» y BORRA el <style> anterior: un cambio de
modo con semilla que no deriva no solo fallaba sin log, desvestia la pagina
del bloque que ya estaba bien puesto. Ahora `#lastSchemeCss` conserva el
ultimo bloque bueno, el catch avisa por `#uix?.logger.warn('eidos.scheme',…)`
(la superficie que ya usa sema; eidos no tiene logger propio), y retirar el
spec sigue limpiando. `applyColorScheme` no cambia: construye EAGER y una
semilla invalida sigue reventando en la cara del llamador — el silencio era
solo del re-derive.
Dos hallazgos del proceso, anotados: el validador de config es FAIL-CLOSED y
rechaza escalas donantes rotas en la puerta (por eso el rojo de E3 no puede
fabricarse via config: la inyeccion va sobre `buildScheme` mismo, passthrough
real hasta que el flag del test lo revienta — el sitio exacto del throw que la
ficha nombraba); y el stub de matchMedia del test de E2 debe dar un mql POR
QUERY, porque el tracker de reduced-motion del propio dom consulta la misma
ventana.
Ambos tests vistos MORDER el codigo viejo (stash de la implementacion, no del
test): E2 porque el query de color iba a la ventana global (jsdom: sin
matchMedia), E3 porque el bloque desaparecia.
Verificado: eidos 28/28 · sema+morfo+eidos+contracts 943 ✓ / 6 ajenos · check
69 = base aislada, diff VACIO · prettier: active-eidos.svelte.ts estaba limpio
en HEAD y queda limpio; el test ya fallaba en HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
58695cd08a |
fix(morfo): M4 — la pareja [true,false] solo se infiere donde es exacta
`compileDataPlan` precalculaba el par [etiqueta-true, etiqueta-false] para todo
stateRef+enum con `falseLabel = values.find(v => v !== trueLabel)`: exacto en un
enum BINARIO (el otro miembro, da igual el orden) y dependiente del ORDEN de
declaracion desde 3 valores.
El diagnostico de la ficha se corrigio dos veces con medida:
1. «Hoy no muerde porque los stateRef booleanos usan enums binarios» — falso de
premisa: hay 12 declaraciones con 3+ (aura x3, checkbox x2, image, meter x2,
progress x2, tooltip x2). Pero tampoco muerden, por OTRA razon: bindean
strings y el runtime solo usa la pareja con `typeof raw === 'boolean'`. En
los 12 la pareja se calculaba y jamas se usaba.
2. El arreglo de la ficha («stateRef+enum exige exactamente 2 valores») habria
PROSCRITO esas 12 declaraciones legitimas — la misma clase de error que M5.
El peligro real es el emparejamiento booleano↔enum-no-binario, hoy inexistente,
y el arreglo lo hace imposible EN SILENCIO: con 3+ valores la pareja no se
calcula, un string pasa intacto (la clase viva), y un booleano LANZA
MorfoInvariantError nombrando componente-attr y la salida declarativa — que no
es un campo nuevo sino `v.mapRef(source, { true, false })`, que ya existia en
el vocabulario. Doctrina S-09: un path malo lanza, no inventa (la alternativa
era estampar `data-state="true"`).
Del re-analisis, dos rectificaciones propias que quedan anotadas: mi primera
propuesta invocaba un «guard de contratos» que NO existe (el mecanismo honesto
es el throw), y mi censo de bindings verifico 2 de 12 — la prueba real es la
suite entera: soma navegador 1278✓/1 (el timeout ajeno) con la pareja ya
retirada de los 12, ninguno lanzo.
Tests nacidos en rojo (2/3; el binario paso porque es la conducta de hoy):
binario exacto con orden invertido · 3+ con string = passthrough · 3+ con
booleano = throw con /mapRef/.
Verificado: compile 44/44 · sema+morfo+contracts 550 ✓ / 6 ajenos · soma
navegador 1278✓/1 · check 69 = base aislada, diff VACIO · prettier: ambos
ficheros ya fallaban en HEAD, no se reformatean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b53c93e422 |
feat(morfo,sema): M5 — el matcher `state` habla el contrato de datos del morfo
El matcher aceptaba pares de strings sueltos con la promesa escrita de que «una
iteracion futura los tipara contra el contrato». La iteracion es esta, y no es
UNA puerta sino DOS — el precedente de dos ejes del framework
(allowedTargets/targetFallback, intentRequirement/intentGuidance):
- `state` se tipa con `DataPairOf<M>`, la union discriminada derivada de
`parts[].data`: attr declarado, y donde hay enum, valor del enum. Un typo en
`data-last-action` deja de compilar — la clase de deriva que semaSelector
existe para matar. La union atraviesa intacta el idioma
`Parameters<typeof semaSelector<M>>[2]` de los 67 packs: cero migraciones.
- `undeclaredState` es la puerta ABIERTA con nombre: los attrs que el morfo no
declara A PROPOSITO (data-size/data-sheet del dialog, eidos-only por
docblock). La regla que la usa dice lo que hace, en vez de colarse por un
string abierto. Un solo slot no podia imponer enum Y quedar abierto — la
rama abierta se traga a la estricta (la clase S-11).
El gate de disyuncion vive en el BUILDER, no en el censo (desviacion declarada
del punto 4 firmado, a mas fuerte): attr declarado por la puerta abierta lanza,
attr no declarado por `state` lanza, valor fuera de enum lanza — y como los
packs son modulo, revienta al IMPORTAR: ninguna suite queda verde encima.
`aria` queda abierto con la razon real escrita: su unico uso en el framework
casa el `role` del dialog, que el morfo deliberadamente no declara
(variant-dependent, lo pone el provider).
Nacidos en rojo: 4 tests runtime (enum, attr no declarado via state, attr
declarado via puerta abierta, puerta abierta funcionando) + probes
`@ts-expect-error`. Migrados los 2 usos de dialog y los 3 tests de escaping que
usaban attrs no declarados. Una arista de implementacion documentada: dentro
del cuerpo generico `DataPairOf<M>` es condicional diferido y TS no deja leer
`.attr` — una lectura estructural local, como ya hace el resto del builder.
⚠️ Nota de proceso: mi primera propuesta fue un guard de censo + corregir el
comentario — el mismo error que S-33 (lint donde el tipo puede hablar), y cai
en el en el mismo dia. El re-analisis contra las decisiones del framework lo
invirtio. La ficha M5 queda anotada como INCOMPLETA, no refutada.
Verificado: selectors 15/15 · sema+morfo+contracts 547 ✓ / 6 ajenos · check
69 = base aislada, diff VACIO · docs 0/624 · prettier: dialog.ts era mio y
queda limpio; selectors.ts/test/index ya fallaban en HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
13246a2c23 |
refactor(sema,morfo)!: M6 — los alias deprecados mueren; un nombre por concepto
`IntentExpectedFamily` y `SemaEventLabel` eran alias de compatibilidad de `IntentRequiredFamily` y `SemaEventKey`, y el codigo nuevo seguia importando el nombre viejo — la mitad de los tipos hablaba el vocabulario de antes del renombrado. La regla dura del repo (no backward-compat shims: actualiza los consumidores y borra el camino viejo) decide el destino: 13 usos migrados a los canonicos y los DOS alias borrados del tipo y del barrel. La unica duda doctrinal se resolvio antes de tocar: D.3 (book-deviations:489) nombra `IntentExpectedFamily`, pero DESCRIBE la implementacion con el nombre que existia entonces — registro historico, no prescripcion. El precedente es la casa misma: se ha renombrado vocabulario entero sin reescribir los registros de decisiones. Ademas, y declarado en la exposicion: `semaIntentExpectedFamilySchema` (const local de morfo/schema.ts, mismo vocabulario viejo) pasa a `semaIntentRequiredFamilySchema` — y el guard S-34 de contracts.test.ts, que lo busca por nombre LITERAL, se actualiza en el mismo commit; separarlos habria dejado el guard ciego un commit entero. Los 2 `as never` de compile.ts se retiran: eran vestigio de antes de que existiera `DepSink` (un `() => void` encaja en `(value: string) => void`), y el compilador lo confirma. Y `_resetCompileCache`, tercera pata de la ficha, resulto YA borrado — cero apariciones en src/; se anota para que no vuelva a la cola. Censo previo al borrado: cero consumidores de los alias en src/, web/ y scripts/ fuera de los 13 migrados. Las funciones `is/parse/toSemaEventLabel` y el tipo `ParsedSemaEventLabel` conservan su nombre: son API propia, no el alias, y su renombrado quedo explicitamente FUERA de lo firmado. Verificado: sema+morfo+contracts 541 ✓ / 6 ajenos · check 69 = base aislada, diff VACIO · prettier: types.ts era mio y queda limpio; compile.ts y contracts.test.ts ya fallaban en HEAD y no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
4e7d0be670 |
fix(sema,docs): S-19 — el puerto de anuncios encaja con su sumidero sin adaptador
`AnnounceFn` pedia la prioridad en una bolsa de opciones y `ActiveUix.announce`
la toma POSICIONAL, asi que el cableado que la doctrina prescribe —una sola
region viva compartida, este canal como uno de sus dos emisores— no se podia
escribir: `{ announce: uix.announce }` no compilaba. Y forzarlo era peor que no
tenerlo: el sumidero recibia un OBJETO donde lee una prioridad,
`liveRegionIds[obj]` es `undefined`, y TODO aterrizaba en la region polite — un
`threat` dejaba de interrumpir, que es justo lo que un aviso critico no puede
hacer.
La firma pasa a posicional, como el sumidero y como la fuente equivalente de
soma. Tres puertos, una sola forma. Cambio de conducta: CERO — el canal es
opt-in y ninguna raiz lo enciende hoy.
Visto en rojo antes de tocar: un test con la forma EXACTA de `uix.announce`
(`(message, priority?, timeout?)`) recibia `{priority:'assertive'}` en el hueco
de la prioridad. Tres aserciones existentes migran de bolsa a posicional — su
contrato cambia, y era el contrato defectuoso.
⚠️ Mi analisis inicial estaba equivocado y el autor lo mando revisar. Habia
concluido que el canal estaba muerto porque el runtime de soma no mete `message`
en la señal. No lo mete, cierto, pero es DISEÑO: son dos emisores para dos
publicos —soma cubre sus componentes, el canal cubre a quien usa
`EngineSemantic` sin soma y escribe su propia señal— y precisamente por eso
nada se anuncia dos veces. La propuesta de fusionarlos habria roto ese diseño;
retirada antes de escribir una linea.
`sema.md` §announce reescrito: fuera el aviso del adaptador (ya no hace falta),
dentro la razon de los dos publicos, y el aviso que SI queda — ninguna raiz
enchufa el canal, y encenderlo sin inyectar el anunciador añadiria un segundo
par de regiones vivas junto al compartido. Eso es decision aparte, sin firmar.
Verificado: announce 6 ✓ · sema+morfo+contracts 541 ✓ / 6 ajenos · check 69 =
base aislada, diff VACIO · docs 0/624 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
455260ca34 |
fix(sema): S-30/S-38 — las cinco reglas hapticas abren la puerta que habian olvidado
Cinco reglas escribian una firma haptica sobre familias cuyo activeChannels es
['sound'] — alertdialog (pulse 0.7/60), sheet movil (tap 0.4/24), editable
shift-enter-mode, stepper shift-step y timeline emerge-reveal. El resolver
instalaba la firma, HapticChannel salia por la puerta de entrada: vibrate() 0
veces desde su nacimiento, con la intencion comentada en los propios packs.
La decision se tomo contrastando con el mundo real, no por limpieza: mi
propuesta inicial era borrar los bloques y el autor la corrigio con las
preguntas correctas — ¿que hacen las plataformas? ¿quien decide? Verificado en
las fuentes: Apple HIG prescribe haptic de warning cuando aparece una alerta
importante y tick de seleccion para cambios de valor discretos; Android pide
moderacion pero con constantes CONFIRM/REJECT. O sea: el alertdialog y el
stepper SON patrones de plataforma, y la intencion escrita en los packs era
diseño, no deriva. El framework ademas ya deja la ultima palabra a quien toca:
la puerta por regla (`channels`), la per-emit, y la haptica entera es opt-in
del producto — ensanchar estas reglas no impone vibracion a nadie.
Arreglo: `channels: ['sound', 'haptic']` en las cinco, cada una con su razon
perceptual escrita. Los 5 waivers de CHANNEL_EXCEPTIONS se retiran: el guard
del censo (punto 2, que estos waivers silenciaban desde 2026-08-06) queda
re-armado y ES el aviso automatico para la proxima vibracion con puerta
cerrada. El aviso del waiver («widening requires completing the firma») estaba
ya pagado por la defensa S-31 de kindToPattern: un {kind:'tick'} parcial
resuelve a valores finitos, nunca vibrate(NaN).
Visto en rojo DOS veces antes de verde: el censo sin waivers listando
exactamente las 5, y el e2e nuevo de resolver.test.ts (resolucion con el pack
real + HapticChannel real: 1 vibracion por regla, patron finito) ejecutado
contra los packs sin ensanchar via stash.
Verificado: censo 67/67 · resolver 30/30 · sema+morfo+contracts 540 ✓ / 6
ajenos · check 69 = base aislada, diff VACIO · prettier limpio · docs 0/624.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
cb1fd54d10 |
docs(process): handoff nuevo para la cola de auditorias, con el protocolo de trabajo delante
El eje perceptual quedo completo hoy (F1-F6), asi que lo que sigue ya no es «su» cola: es la cola de auditorias — sema, fable, blocks, eje B, chronos C5. Este handoff la lleva, y pone PRIMERO lo que cambio hoy de verdad: el protocolo. §0 · las cinco preguntas del autor antes de CADA accion, un item por mensaje, y parar a esperar firma. Sin puerta que permita auto-autorizarse lo mecanico: lo propuse y se corrigio en el acto. Con las dos precondiciones que hoy costaron dos rectificaciones completas — el SPEC del COMPONENTE es doctrina y se busca ANTES de diagnosticar (chronos), y se barre `docs/` antes de proponer retirar nada (S-33 esta en CANON.md y en la D.3 firmada). La regla que las resume: el codigo nunca corrige al canon; se le pone al autor la contradiccion delante. §1 · S-33 FIRMADO y sin ejecutar, con la receta y la evidencia del probe de `tsc`: el defecto es el `Exclude`, no «falta una rama». Derivar los tres cubos positivamente cierra la puerta sola al flipar el const, y la tercera rama es inerte hoy porque se teclea sobre `never` — medido, delta de conducta CERO. §2 los 6 commits de hoy · §3 las tres decisiones abiertas con su evidencia ya medida (S-35 · S-30/S-38 · S-19, esta ultima partida en dos porque (ii) cambia conducta) · §4 la cola en orden · §5 la base de verificacion, con los 6 fallos ajenos de contracts nombrados uno a uno · §6 las trampas medidas hoy, para no re-descubrirlas: la laxitud DELIBERADA del guard D9 (endurecerlo convierte 27 emisiones reales en violaciones), el panel oculto congelando la linea de tiempo, y los ficheros ya sucios en HEAD para prettier. docs:check 0/624. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
a157454913 |
docs(process): la 6a fila de chronos no era un defecto — decision revocada por el SPEC
El item llevaba un mes en la cola como «la 6a fila fantasma» y estaba FIRMADO para arreglarse. Al ir a ejecutarlo aparecio el SPEC del propio componente, que dice lo contrario en TRES sitios coordinados: rejilla de 6 filas constante «estabiliza la altura» (SPEC.md:270), inventario de reuso pidiendo «rejilla de mes 6x7» (:492), y `data-outside-month` como estado declarado de `day-cell` CON su propio token de tema (:311 y :461). Las celdas de fuera de mes son diseno, no relleno. La asimetria con la familia —chronos `true`, los otros cuatro `false`— se leyo como deriva y es al reves: chronos es el unico que escribio su decision, y coincide con el default de sus dos referentes de libreria, verificados en la fuente y no citados de memoria: FullCalendar `fixedWeekCount` viene `true` («the calendar will always be 6 weeks tall») y Toast UI `month.isAlways6Weeks` viene `true`. Google/Apple/Outlook si varian las filas, pero porque son aplicaciones que llenan el viewport; chronos no tiene contrato de altura, asi que la receta propuesta tenia que inventarse la altura con `calc(6 * cell-min-block)` — la altura de 6 filas. Era la misma altura con 5 filas mas gordas, y menos informacion. Medido antes de opinar: hoy son 115 px por fila y 724 px de rejilla en todos los meses; con `fixedWeeks:false`, jul→dic 2026 da 609/724/609/609/724/609, o sea el salto de 115 px que la decision queria evitar. Y crecer las filas no ensenaria ni un evento mas: `maxLanes` es `$state(3)` fijo. Corregidos ademas dos errores de la ficha original: el default vive en DOS sitios (chronos.svelte:20 y engine/state.svelte.ts:64), y chronos.css:278 no es la altura de las filas del mes sino la pista de carriles de chips dentro de una fila. La conducta de Google queda anotada como FUNCIONALIDAD con firma propia (contrato de altura + maxLanes derivado), no como flip de flag. docs:check 0/623. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c00e97b91a |
docs(process): F5 cerrada — los tres sitios de las tres auditorias, medidos DESPUES de la campana
F5 no era «volver a medir cada ola»: es la comprobacion de regresion del eje
entero sobre los tres sitios que fable S1, sema S-17 y blocks A-36/A-65
encontraron por separado — y ahora con la API de emision ya borrada.
site-header 375px contact-activate TRIGGER +8,4ms · emerge-open CONTENT +10,3ms,
press-squeeze corriendo SOBRE el trigger, close en el content
knob handle-drop y commit-set separados por 245,4 ms
dropdown+Button +109 / +112,7 ms, close en content, commit-select en el item
PULSADO («Log out», 3.º de 3)
El numero que cierra S-17 es 245,4: el commit encolado entra justo tras el hold
de 240 del drop — ni aplastado a los 24 ms que S-17 midio, ni 1,6 s tarde como
midio el falso verde de `queue`. Y los tres sitios son la misma geometria: un
elemento con DOS morfos (trigger+button, control para drop+commit).
Anotados los dos artefactos del entorno para que no vuelvan como hallazgos: con
el panel sin componer, rAF esta suspendido (el handle-drag continuo del knob no
estampa nunca) y la linea de tiempo de las animaciones no avanza (el
desestampado cae SIEMPRE en el tope de 1500 ms de la espera de expresion, de ahi
los ~1,7 s de limpieza en las tres trazas). getAnimations() dice que corre sobre
que; para duracion de pintura hace falta Chrome real.
docs:check 0/623.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b8aa333fd1 |
feat(soma,morfo)!: targetOverride BORRADO — el estado ilegal del eje es inexpresable
El endgame que §3.0 firmo al abrir F3: con el censo a 0, la opcion de elemento anonimo sale de TriggerOptions, y con ella assertTargetOverride (existia solo para vigilarla; queda assertAnchor, el guard de la puerta que la sustituye). Las dos resoluciones `opts.targetOverride ?? resolveEmitTarget(...)` (emit y foco a11y) quedan en la resolucion unica, y los Omit<..., 'targetOverride'> de los triggers anclados se vuelven vacuos y desaparecen. El tipo lleva LAPIDA doctrinal: la opcion nacio fallbackTarget, se renombro el 2026-08-10 como precondicion del censo, y compensaba un registro sin identidad de instancia — es lo que dejo a tres auditorias (fable S1, sema S-17, blocks A-36/A-65) encontrar la misma deriva sin poder cerrarla. No volver a añadir una opcion de elemento. Y la tesis se probo sola al ejecutarla: el compilador, ya como censo, cazo 9 usos en TESTS que el grep del scratch nunca escaneo. De ellos, 3 eran redundantes (la parte registraba el mismo elemento), 2 del test de metrics migran al registro + trigger plano, y 4 fijaban la conducta borrada — el describe «targetOverride contract» entero y el test del override preferente, retirados con lapida; el guard A-36 de los overlays (morfo sin redireccion + post) se conserva, des-anidado. El scratch del censo, retirado: el censo es `npm run check`. Docs vivos adjudicados: morfo.md §allowedTargets describe la puerta anclada (EventNameTargeting + assertAnchor) y §repeated-part nombra partInstance donde decia targetOverride; los docblocks de las 3 factorias de vistas de eidos dicen la forma nueva; errors.ts deja de ofrecer la opcion muerta como remedio. Las menciones historicas (A-36, «the retired...») se conservan como historia. Verificado: soma navegador 1278✓/1 (el timeout ajeno preexistente de soma-attr-audit, documentado en la base) · runtime+metrics 53/53 · check 69 = base, 0 propios · docs:check 0/623 · prettier limpio en lo que estaba limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
e1cedb81ba |
fix(soma): el dropzone de file-upload deja de robar clicks ajenos — un gesto, una ocurrencia
Lo habia dejado ANOTADO en la ola 6 por no cascadear, y estaba mal clasificado: es la clase que este eje persigue (una ocurrencia por gesto), encontrada por mi propia medicion y en un fichero que ya estaba editando. Y al medirlo de verdad, el mecanismo resulto MAS ANCHO que el diagnostico de la nota. No es solo el Trigger burbujeando: el <input type=file> oculto vive tambien DENTRO del Dropzone, asi que el click() SINTETICO que dispara openPicker vuelve a entrar por el mismo manejador. Medido en navegador: UN click en el dropzone llegaba DOS veces, la segunda con target = INPUT[hidden-input] — solo el guard de reentrada que la plataforma pone en click() lo paraba ahi. O sea que TODO click al dropzone duplicaba, no unicamente el del boton. Arreglo: guard por PROPIEDAD en el onclick del dropzone — `closest` sobre los marcadores de `trigger` y `hidden-input`, construidos con partMarkerAttr y nunca a mano, para que un renombrado de parte rompa aqui en vez de ensanchar el alcance en silencio. Es el idioma que table ya practica. Test visto FALLAR con la anidacion real del DOM (trigger + hidden input dentro del dropzone) y los tres casos: la superficie propia abre, el input oculto no re-abre, el boton no re-abre. Medido despues en navegador: click en el dropzone → UN contact-trigger-picker en DROPZONE; click en el boton → UNO en TRIGGER. file-upload 5/5 · check 69 = base, 0 propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
4854df3c47 |
refactor(morfo,soma): ola 6 de F3 — las colecciones, y el censo baja a 11 (solo palabras)
Ocho componentes, cuatro clases, cada sitio con la pregunta A-36 primero:
NO-OP DE SINGLETON, retirados: virtual-list ×2 y virtual-grid (viewportRef lo
escribe la propia registracion del viewport) y css-field signal-warn-invalid
(el elemento ERA runtime.partRef('input'), el destino declarado — se lee para
clearTarget, no para redirigir).
ANCLAJE POR IDENTIDAD en las repetidas: menubar trigger · navigation-menu
trigger ×2 + link · tags-input item · file-upload item. En color-field y
time-field no es adorno: DOS partes registran el kebab `input` (grupo visible
+ input oculto), y fieldNode es el ref de la visible, asi que partInstance la
nombra sin depender de quien monto ultimo.
TERMINALES devueltos al provider declarado: tags-input commit-set-add ×3 y
commit-reset; file-upload commit-set-add, signal-warn-reject y commit-reset.
El dropzone que recibe el drop y el boton que ordena el borrado son la
superficie del GESTO, no el sujeto de la ocurrencia — la fila de la doctrina
del sello. Con ellos caen los parametros `target` muertos y sus llamantes.
allowedTargets NUEVO, y es el eje correcto: file-upload contact-trigger-picker
declara [dropzone] junto a su target `trigger`. Es familia contact — el gesto
se sella donde esta la MANO — y las dos superficies son eleccion del llamante
en operacion normal, no estado de montaje.
Medido en navegador: nav-menu abre y cierra sobre el trigger PULSADO
(«Solutions», el 2º de 2, que es la prueba de la identidad) y commit-select
sobre el link «Pricing»; file-upload sella DROPZONE al pulsar la zona y
TRIGGER al pulsar el boton.
⚠️ Hallazgo anotado, NO tocado (preexistente, verificado byte a byte contra
HEAD): pulsar el Trigger de file-upload emite contact-trigger-picker DOS veces
— vive dentro del Dropzone, cuyo onclick no filtra el click burbujeado. Ficha
en el handoff; el arreglo es un guard de propiedad y no se cascadea aqui.
Verificado: suites 46/46 · censo 29 → 11 (solo palabras) · check 69 = base, 0
propios · docs:check 0/623 · prettier limpio en lo que estaba limpio en HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
aa8a630ed2 |
fix(morfo,soma): ola 5 de F3 — select y combobox dejan de sellar el trigger, y present-rise suelta el boton
El superviviente A-36 mas puro de la campaña: el open YA era post (el content
monta antes del emit) pero el provider seguia forzando targetOverride al
trigger — un vestigio de la era pre LEGALIZADO por el allowedTargets del
2026-08-10, que era el eje equivocado (estado de montaje, no eleccion del
llamante). Con el, la firma de motion present-rise corria SOBRE el boton: la
regresion firmada en §1.2 del handoff de normalizacion, medida hoy.
A/B en navegador (la disposicion firmada mandaba medir primero):
ANTES emerge-open TRIGGER · emerge-close TRIGGER · present-rise en el boton
AHORA emerge-open CONTENT · emerge-close CONTENT · present fuera del boton
commit-select → el ITEM elegido («Pear Soft»), anclado por identidad
Receta (la de F4/ola-1): el open pierde allowedTargets y el override; el close
declara targetFallback ([trigger] en select, [trigger, input] en combobox — un
combobox puede vivir sin boton); la seleccion ancla por partInstance al item
registrado, con el nombre computado de ListSelection (SelectionEvent) por la
puerta tipada.
Verificado: suites 24/24 · censo sin un solo sitio de select/combobox ·
check 69 = base, 0 propios · consola limpia (404s = raiz/favicon del arranque).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
69b17f741c |
refactor(eidos,soma): ola 4 de F3 — las vistas de eidos REGISTRAN sus partes (censo 38 → 29)
El unico grupo que no era retirar-o-anclar: metrics, menu-dial y onion-menu
emitian con elementos locales que ningun registro conocia, asi que
partInstance daba null y el override era la unica via. Ahora registran:
- metrics: `value` se registra (id + ref del registro) y emite su
signal-notify-update ANCLADO el mismo — el contexto gana `live` + `runtime`
y deja de acarrear elementos crudos (notifyUpdate muere).
- menu-dial: `list` registrado en la raiz (open/close resuelven solos) y cada
`action` registrada en su componente — el attachment del part atraviesa la
cadena Fab→Button→<button> compuesta, medido: los mini-FABs llevan ids del
runtime y el commit-select aterriza en LA accion pulsada.
- onion-menu: `surface` registrada sobre surfaceEl (SVG cabalga el ref
HTMLElement con el mismo cast que usaba el asTarget retirado) y cada sector
`item` por attachment CACHEADO por clave — la membresia es el stream del
attachment (F2): el drill poda y re-añade sin crecimiento. asTarget muere.
Medido en navegador los tres: onion open→SURFACE, expand→sector d1 «Create»
(el pulsado), select→hoja d2 «Note»; menu-dial open→LIST + contact→trigger
(dos superficies expresando); metrics update→el Value del bloque live con 4
instancias registradas — no el mas nuevo, que es la prueba de que el anclaje
importa. Consola limpia.
⚠️ Trampa pagada y anotada: importar `state` de $libs/reactive en un .svelte
que usa la runa $state rompe TODOS los $state<T> del fichero (shadowing) —
alias `state as refState`.
Verificado: suites 19/19 + metrics 3/3 · censo 38 → 29 (−9, cae tambien el
FOREIGN) · check 69 = base, 0 propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
14d0dee4f2 |
refactor(soma): ola 3 de F3 — css/number-field pierden el targetOverride publico (censo 44 → 38)
La QUEDA pedia retirar targetOverride de la API publica de commit(), y el
censo de llamantes dio la razon entera: NADIE externo lo pasaba — solo los
propios scrubbers en pointerup/lostpointercapture, redirigiendo el TERMINAL
commit-set (declarado provider: «the terminal is stamped where the value
lives») al elemento del scrubber. Clase A-36: sobra la redireccion. Y no era
gratis: con el sello en el scrubber, las reglas de pack que seleccionan el
provider no casaban jamas un commit de scrub — la clase TextArea de S-12.
Tambien fuera los 4 handle-pick/handle-drag-scrub con e.currentTarget: el
scrubber es singleton y registra, el override re-decia el destino declarado.
commit() queda { clamp?: boolean }; los finales de scrub llaman sin bolsa y
los handlers sueltan el parametro e que ya no leian.
Verificado: suites 17/17 · censo 44 → 38 (−6 exactos) · check 69 = base, 0
propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
3fdaa8cbb6 |
refactor(soma): ola 2 de F3 — los grids de fecha anclan por identidad (censo 51 → 44)
Clase B entera (el override elegia la instancia correcta de una parte repetida): la eleccion es legitima y se conserva, expresada por la puerta tipada — partInstance + trigger anclado, que verifica la identidad contra el registro en vez de contrabandear un elemento crudo. - calendar: commit-select/unselect anclados al `day` pulsado (1 sitio, 2 eventos). - range-calendar: 5 sitios → helper privado triggerOnDay (select-start · select-range · reset ×3), `day` registrado en :1206. - month-grid y year-grid: commit-set anclado a la `cell` pulsada. Un elemento no registrado o ausente cae a la resolucion del runtime, como en las olas anteriores. Verificado: suites 22/22 · censo 51 → 44 (−7 exactos) · check 69 = base, 0 propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
f6e4f40723 |
refactor(soma): ola 1 de F3 — los gestos handle sueltan targetOverride (censo 64 → 51)
Los cuatro componentes de gesto con «campos de elemento propios», y la pregunta A-36 primero en cada sitio: - path-trace y rotate-align (3+3): tokenEl/trackEl y needleEl/dialEl SON los elementos que sus partes registran (los escribe la registracion) — el override re-decia el destino declarado singleton. No-op puros, retirados. - float-panel (4): la QUEDA preguntaba ¿allowedTargets o retirar? y la respuesta estaba en la fila de la doctrina del sello: el content ES la posicion (grip y terminal, morfo.md §Where the stamp lands). contentRef es el elemento registrado del target declarado y el ?? dragEl/resizeEl era cinturon de la era fallbackTarget, inalcanzable durante el arrastre del propio panel. Retirados. - drag-drop (3): draggable y droppable son partes REPETIDAS con la instancia correcta en la mano — anclados por identidad (partInstance + trigger anclado, el idioma de tag-group). Verificado que source/target son exactamente los refs registrados (opts.ref.current en los dos call sites), asi que la identidad resuelve; un elemento ajeno cae a la resolucion del runtime. Verificado: suites navegador de los 4 → 34/34 · censo 64 → 51 (−13 exactos) · check 69 = base, 0 propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
281840db3e |
refactor(morfo,soma): alert-dialog cierra el eje del nombrado — el default es del CONTRATO, cero cadenas mecanicas
La ultima cadena clase 1 del catalogo, excluida del barrido de los 21 porque otra sesion editaba el fichero. Receta identica (morfo.md Step 4): el morfo declara #?components.alert-dialog.action|Confirm y .cancel|Cancel; fuera la cadena resolvedAriaLabel x2, la fuente del runtime, el prop de opts y el destructure de los dos wrappers — el aria-label del consumidor fluye por restProps y gana por politica de merge. ALERT_DIALOG_LANGS queda huerfano (su langs.ts se suma a la lista declarada, 14 → 15; no se borra). El harness del test se alinea con produccion de paso: translate = ts (un solo traductor para las dos puertas — la trampa del harness de editable, donde la identidad dejaba pasar el ref crudo por la cadena resuelta). Medido: navegador en es → aria-label="Confirmar" / "Cancelar" saliendo del contrato, type="button" intacto · translations:check 159 refs compilados (157+2), 0 errores · alert-dialog 2/2 · check 69 = base, 0 propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
5f024b1733 |
docs(eidos,process): el ancho minimo de bar queda escrito, y el handoff deja de deber tres decisiones
README de eidos: bar tiene ~900px de ancho minimo de diseño (medido
2026-08-04) — valor de la variante, como los tamaños de caratula — con la
disposicion firmada del arreglo real: ORDEN DE DESCARTE (bajo el umbral cae el
slider de volumen y el mute recupera sus 36px) via container query, el primer
caso concreto para reabrir D-AP2.9.
CONTINUE-sound-engine adjudicado en tres puntos: el rojo de audio-player ya
estaba RESUELTO en el codigo (categoria COMPETING_ROOTS, lint.test.ts:70, con
la razon escrita — verificado 18/18); el aria-pressed del caption-button queda
FIRMADO (b) y ejecutado (
|
2 months ago |
|
|
653eb5fa3c |
docs(process): el switch Sound del topbar estaba cableado desde d161cedcc — medido hoy, y los handoffs dejan de decir «no tocado»
El hallazgo del 2026-08-04 quedo rancio dos dias despues: |
2 months ago |
|
|
fa79cc5423 |
fix(sema): con sound 'off', prepare ya no abre el AudioContext — la reduccion alcanza la puerta (S-27/S-41)
El gate de shouldPrime solo miraba signal.channels: con la preferencia en 'off' cada emit seguia creando el contexto y registrando los listeners globales de desbloqueo — 'off' silenciaba la salida pero pagaba igual la adquisicion del recurso, contra la cabecera del propio canal y la promesa del art («an app that never makes a sound pays nothing»). La preferencia se lee POR LLAMADA, asi que volver a encenderla prima en el siguiente gesto sin suscripcion. 'reduce' sigue primando (suena, atenuado). El activeChannels de la familia NO se consulta a proposito: prepare corre antes de resolver la cascada y una regla de pack puede ampliar los canales (el 'step' de los sliders cabalga justo eso) — esa mitad de S-41 queda rechazada con la razon escrita en el comentario. El test del canal nacio en ROJO (el de 'off' existente solo ejercitaba handle, que es como el agujero paso inadvertido) y fija ademas la vuelta: off no prima, flip a full y el mismo gesto siguiente prima una vez. chans+sound 84/84. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
4a1a0c3907 |
refactor(sema): los 3 resolvers de gesto se retiran — esta vez desde el eje, y el registro se hace verdad
El rediseno del 2026-08-06 (gestos por REPETICION, sound:'step' por emision)
los dejo sin llamador, y book-deviations D.7 escribio en pasado un borrado que
nunca ocurrio. Hoy ocurre, firmado y desde una sesion del eje que empezo por
su handoff — la condicion que la retirada revertida de
|
2 months ago |
|
|
b7784d34a3 |
docs(process): la ronda de decisiones queda firmada — cinco disposiciones, dos corregidas al re-analizar
D10 SE RETIRA (lo ejecuta el eje de sonido, su handoff PRIMERO), y el re-analisis añade un SEXTO sitio a adjudicar: sema.md §per-emit payload aun describe pitch/gain/contour cuando soma adjunta hoy sound:'step' + curva haptica (dragSignalOverrides del slider y del knob, con el trade-off del 2026-08-06 citado en su comentario). fixedWeeks: alinear a false a secas era la opcion debil — las filas del mes son de altura FIJA (grid-auto-rows, chronos.css:278) y un false pelado cambia la semana fantasma por saltos de altura al navegar. Firmado: crecer las filas (modelo Google), con false incluido; es arreglo de RECETA, no un flip de flag. Chip bloqueado: via (b), y la letra pequeña va INCLUIDA — el morfo de drag-drop YA declara data-disabled (declaracion sin emisor) y el cursor vive en drag-drop.css; pero el chip fusiona tres morfos en un elemento clicable, asi que la regla de archetype item se anula en su receta y aria-disabled NO aterriza en el elemento fusionado. mode/scope: SE RETIRAN, con el barrido de la regla 4 hecho de verdad: 0 hits en docs/, 0 lectores del compilado, 4 ficheros de alcance. SemaCause queda como pregunta hermana (mas muerto aun: ni atraviesa morfo). present-rise: medir primero (lo esperable es la receta F4, pero decide la medida). handle-drag-progress: revisar contra CANON, no renombrar — el matiz es legal y puede ser deliberado. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d7b339f774 |
docs(process): cerrar el plan y sacar lo demas a su propia iniciativa
El plan de normalizacion queda CERRADO: su aceptacion se cumplio y esta medida (256/256, guard visto fallar, barrido de 172 morfos). Lo que el barrido destapo NO es este eje. El handoff pasa de listar tareas a listar DUEÑOS: D10 al eje de sonido, D13 a la auditoria de sema y al vocabulario de morfo, D14 a chronos, y las dos decisiones al autor. Nada se ejecuta desde ahi sin abrir su propia iniciativa. Queda escrita la leccion de la jornada: un handoff no es un backlog. El ledger se creo a las 03:55 para una sesion futura y a las 13:59 esta misma sesion lo empezo a consumir como lista de trabajo, hasta acabar borrando una libreria de otro eje (revertida). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
4ed53f269e |
docs(process): dos correcciones de registro — mi censo repetia uno que ya existia, y peor
D13 mezclaba tres clases en un saco
------------------------------------
`CONTINUE-sema-audit.md` §3.1 YA habia medido esto — «eventos huerfanos: 14 de
249 en 104 morfos con soma» — y, sobre todo, ya los habia CLASIFICADO. Verificado
componente a componente, su clasificacion es la correcta y la mia era gruesa:
- **contrato muerto** — `tooltip` ×3. El unico defecto de la clase D1/D3/D4, y el
unico accionable. Cero `trigger(` en todo su directorio soma.
- **punto de extension documentado** — `virtual-list` / `virtual-grid`
`handle-scroll*`. Los emite la APP; el README los diseca en prosa con su
doctrina. Lo que falta no es cableado: es que el morfo no tiene forma de
DECLARARLO (la auditoria propone `emission: 'app'`).
- **verbo delegado** — `gradient-picker.commit-reset` (S-14, 1 de 6 identicos).
NO se arregla como D3: `GradientPickerProvider` no tiene `clear()` — su
docblock dice que open/commit/cancel/clear viven en el `PickerProvider`
generico compuesto y que esta clase «solo posee el runtime del morfo». No hay
donde enganchar.
Lo que el guard añade sobre aquel censo no es el hallazgo: es que deja de ser una
medicion de una vez y bloquea la regresion. Eso lo demostro D9 al deshacerlo.
`CONTINUE-perceptual-surface.md` afirmaba algo falso sobre chronos
-------------------------------------------------------------------
Su fila de la migracion de `targetOverride` dice «sus chips tampoco REGISTRAN —
plain trigger fallaria igual». No se sostiene, y lo se porque acabo de medirlo
cableando el acarreo (D9): `partInstance('event-chip', el)` RESUELVE, el sello de
`handle-drag` aterriza en el `event-chip`, y el arrastre del tirador da 6
`handle-resize` sobre `event-resize-handle`. Los chips registran.
La otra mitad de la fila (los botones del editor no son partes) queda, y se le
añade lo que dice la auditoria de sema: la exclusion de escritura de chronos se
levanto el 2026-08-10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
0d10b983e5 |
revert(sema): la libreria de sonido vuelve byte a byte — la retirada no era de esta sesion
Revierte la mitad D10 de `5571f3bf7` por orden del autor. La sesion era normalizar nombres de evento; la retirada de los 3 resolvers de gesto nacio de perseguir un homonimo que cree yo mismo al renombrar `SoundDirection` → `SoundContour` sin mirar el namespace de destino, y se ejecuto sin leer primero el handoff vivo del eje (`CONTINUE-sound-engine.md` — la memoria del proyecto lo marca PRIMERO) ni `PLAN-audio-player-v2.md` (gate firmado), que ni siquiera sabia que existia. La verificacion posterior salio limpia — ninguno de los seis documentos no leidos nombra los resolvers — pero limpia por suerte no es limpia por metodo. Restaurados byte a byte a su estado anterior al borrado (diff contra `5571f3bf7^` = 0 lineas): - `sema/sounds.ts` (160 lineas otra vez: resolvers, `DragSoundParams`, `SoundContour`, `clamp`, `lerp`) · `sounds.test.ts` (los 2 tests de arrastre) · `exports.ts` (los 5 re-exports) - las 4 prosas que se habian reescrito describiendo el borrado: `sema/types.ts`, `sema/components/splitter.ts`, `soma/splitter-provider.svelte.ts`, `arts/motion/types.ts` - `scripts/docs-vocabularies.ts` + `docs/canon/vocabularies.md` regenerado - la nota fechada que se añadio al §4 de `PLAN-sound-engine.md` Lo que NO se revierte, porque no depende de la libreria: D9 (el acarreo de chronos) y el guard de eventos inertes — verificados en verde tras el revert. El ledger deja constancia: D10 pasa a CONFIRMADO (retirada REVERTIDA), con el analisis conservado como evidencia PARA la sesion propia del eje de sonido — `git log -S` (un solo commit en la vida de los resolvers), `book-deviations.md` dandolos por borrados el 2026-08-06, los dos planes cerrado/rechazado — y la regla explicita: sounds.ts no se toca sin una sesion que empiece por su handoff. Verificado: sema 300/300 (con los tests restaurados dentro), guard de eventos inertes en verde, `check` 70 errores (linea base), `docs:check` 0/623. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
5571f3bf7e |
feat(soma,sema): el acarreo de chronos habla, los 3 resolvers se van, y un guard cierra la clase
D10 · no era una decision abierta: era una firmada sin ejecutar
---------------------------------------------------------------
Mi correccion de mi propia propuesta estaba sobre-corregida: lei la linea 95 de
un plan y la 367 de otro, no los documentos.
- `PLAN-sound-engine.md` esta CERRADO (2026-07-30) y su columna «sema conserva»
miente en 4 de 5 celdas: `SOUND_LIBRARY` no existe, `SOUND_TUNINGS` se retiro
el 2026-08-06 (con guard que lo mantiene retirado), el sonido salio de la
cascada, y «que firma para que familia × intent» lo sustituyo la ley del
nombre. Es un inventario del 30 de julio, no un mandato.
- `PLAN-audio-player.md` esta ⛔ GATE RECHAZADO — SUSPENDIDO, y su cabecera dice
«nada de lo de abajo se implementa».
- La fuente que gobierna es `docs/decisions/book-deviations.md`, de DECISIONES,
que el 2026-08-06 escribio en pasado: «un gesto continuo suena por REPETICION …
y los tres resolvers se borraron con el». `git log -S` sobre `sounds.ts`
devuelve UN SOLO COMMIT en toda su vida (mayo): nunca se borraron. El registro
dio por ejecutado lo que no se ejecuto.
Retirados los tres, `DragSoundParams`, el `SoundContour` de sema, sus exports,
los dos tests que solo se probaban a si mismos y los helpers `clamp`/`lerp` que
quedaron huerfanos: `sounds.ts` pasa de 160 a 79 lineas. Con ellos cae el
homonimo `SoundContour` — el nombre queda para el art, sin renombrar nada. El
plan cerrado no se reescribe, pero su §4 lleva ya una nota fechada porque me
engaño a mi.
D9 · el acarreo habla
---------------------
`handle` pregunta «¿estoy manipulando directamente este objeto?», y una familia
que no responde su propia pregunta ha fallado en lo unico para lo que existe
(CANON §2). Chronos es el componente mas manipulable del catalogo y hablaba solo
el suelte: el acarreo entero estaba mudo. El *handle invisible*.
No se autora nada — `handle` ya declara `sounds: { default: 'step' }` y los dos
canales, asi que el RITMO es el sonido. Estrangulado a ~14 Hz como slider y
splitter, y ANCLADO: los destinos son partes repetidas, asi que es aplicacion
directa de la ley de `e5811e422`.
Donde el acarreo NO es de chronos: arrastrar un chip con puntero pasa por el
`DragDrop` compuesto, que declara tres eventos y emite tres, sin acarreo continuo
(CANON regla 6). Dos componentes narrando un gesto seria peor. Asi que
`handle-drag` es el acarreo de TECLADO y `handle-resize` cubre las dos vias.
Medido: el arrastre del tirador con puntero da 6 `handle-resize` en 6 pasos,
todos sobre `event-resize-handle`.
El guard, y dos hallazgos que salieron de medirlo
--------------------------------------------------
`book-deviations.md` tipifica el defecto hermano como «mecanicamente comprobable
y merece guard». Este es la misma forma un piso arriba, y es la clase que produjo
D1, D3, D4 y D9. La prueba NO es `trigger('literal')` — chronos despacha por
variable y eso me dio dos falsos negativos durante la auditoria; se comprueba que
el nombre aparezca como literal donde el provider alcance: su directorio soma mas
las librerias compartidas (sin `$libs/selection` en el ambito, combobox y select
se leen como inertes y no lo son).
D13 — quedan 7 eventos inertes en 4 componentes, enumerados en
`INERT_EVENT_DEBT`, que no son exenciones sino la cola. El peor: `tooltip` 3 de
3, cero `trigger(` en todo su directorio, abriendo y cerrando sin firma alguna.
D14 — midiendo D9 salio un defecto anterior: `restoreChipFocus` reenfoca en un
`queueMicrotask` que corre ANTES de que Svelte reponga el nodo, asi que enfoca la
nada y el foco cae al `<body>`. La segunda flecha ya no llega al manejador y el
Escape tampoco: la interaccion que el propio componente anuncia («Grabbed. Arrow
keys move, Enter drops, Escape cancels») muere al primer paso. No lo toco aqui —
es de foco, no de firma, y merece su propia medicion en navegador real.
Verificado: guard visto fallar deshaciendo D9 (caza los dos eventos); `check` 70
errores en linea base; 2250 tests del uix — los 6 fallos de `contracts.test.ts`
siguen siendo de la otra sesion; `docs:check` 0/623 tras regenerar
`vocabularies.md`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
82d269cd8c |
fix(soma): en `modal`, un cierre sin causa es un descarte — y un descarte no commitea
El contrato que los cinco pickers documentan tiene dos salidas: Save confirma,
Cancel revierte al valor con el que se abrio. Escape no es ninguna de las dos, y
tampoco esta bloqueado — descarta en ambos modos por diseño (APG `combobox`; el
framework bloquea Escape en exactamente un sitio, `alertdialog`).
Medido el 2026-08-12: en `modal` el popover no autocerraba al elegir, pero Escape
cerraba Y SE QUEDABA la edicion (05/20 → 05/10). Un tercer camino de salida que
se comportaba como Save, y que ningun documento describe.
Tercera afirmacion falsa de la fila D5
--------------------------------------
Escribi que «`'dismiss'` no lo produce nadie». Lo produce el POPOVER —
`dismissWith('dismiss')` en su manejador de Escape—, no el picker, y por eso el
picker nunca se enteraba. Grepear solo el directorio del picker lo escondio.
El arreglo: la arista de cierre, no la tecla
---------------------------------------------
Interceptar Escape seria fragil y estaria en la capa equivocada: el `Content` es
el del Popover y su gancho de Escape es prop del consumidor, asi que el contrato
del VALOR acabaria viviendo en eidos.
Se vigila la arista de CIERRE: un cierre que no declara causa es un descarte, y
en `modal` un descarte revierte. Eso cubre ademas a un consumidor que baje `open`
a mano, que tampoco es un Save. En `inline` no revierte nada — alli la seleccion
se aplico segun se hacia y el snapshot solo sirve para `cancel()`.
Vive UNA vez, en `watchPickerDismiss` (`picker-shell-handle.svelte.ts`), y los
cinco lo consumen: eran cinco copias del mismo `watch` sobre `open`, con el mismo
`valueOnOpen`. El provider declara su causa desde `closeWith`, asi que `commit()`
y `cancel()` pasan intactos; los de rango pasan su snapshot compuesto tal y como
su propio `cancel()` lo restaura.
Medido, y dos tropiezos propios
--------------------------------
Navegador, date / time / color-picker en `modal`: se edita, Escape cierra el
popover Y el valor vuelve al de apertura.
⚠️ La primera medicion dijo «Escape BLOQUEADO». Era la sonda leyendo a 400 ms, en
plena animacion de salida; la traza temporal muestra el popover aun presente a
+200 ms y ya cerrado, con el valor revertido, a +600 ms. Si me quedo con la
primera lectura, reporto como exito justo la conducta que la doctrina prohibe.
⚠️ Los tres primeros tests pasaban EN VACIO hasta que les añadi `flushSync`:
`watch` no ve una transicion si las tres escrituras caen en el mismo tick, asi
que no observaba ninguna arista. Los cinco estan vistos fallar desactivando la
reversion.
`check` 70 errores (linea base), 2251 tests del uix — los 6 fallos de
`contracts.test.ts` siguen siendo de la otra sesion. `docs:check` 0/623.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
eda25a4866 |
fix(morfo,soma): las 4 ultimas de D11 — el guard se queda sin excepciones
`DEFAULT_ELEMENT_DEBT` esta vacio: las 28 partes que el censo encontro dicen ya la verdad sobre el elemento que renderizan. Estas cuatro pedian un cambio con consecuencias, y la leccion es que «que lado esta mal» solo se contesta mirando. chronos.event-chip — el morfo tenia razon, faltaba el puente ------------------------------------------------------------ El motivo estaba escrito desde el 2026-08-10: el chip CONTIENE el tirador de redimensionado y el modelo de contenido de un `<button>` no admite un control anidado, asi que es `div` + `role='button'`. Lo que faltaba era el «tabindex propiedad de soma» que ese mismo comentario promete y que nunca existio. El provider emite ahora `tabindex: 0` y un `onkeydown` que convierte Enter/Espacio en un clic — con `preventDefault` sobre el espacio, que si no desplaza la pagina— y el componente pasa a ser el `div` que su contrato declara. tooltip.trigger — probe el morfo, me equivoque, y lo corrigio la captura ------------------------------------------------------------------------ Declaraba `button`, asi que renderice un `<button>`. La captura mostro el cromado nativo alrededor del disparador: un recuadro que antes no estaba. La receta llama a esta parte «a focusable shell» y difiere el anillo de foco «to the wrapped element». Es un ENVOLTORIO: el control real del consumidor va DENTRO, y un `<button>` envolviendo un `<button>` es el mismo anidamiento invalido del chip de chronos. El lado equivocado era el morfo. Declara `div`, y la a11y la dan el `tabindex: 0` del provider y el `aria-describedby` del morfo, que es lo que el patron APG de tooltip pide de verdad. stepper.item — no era declaracion, era patron ---------------------------------------------- Un `div` SIN rol dentro de un `role='tablist'`. El patron real es `list (tablist) > item > trigger (tab)`, y un elemento generico entre un tablist y sus tabs rompe la relacion de posesion. Es `role='presentation'` ahora: el envoltorio sale del arbol, igual que un `<li>` dentro de un `role='menu'`. El idioma ya estaba en 8 componentes del catalogo, no lo invento. color-picker.channel-input — una declaracion que nada podia satisfacer ----------------------------------------------------------------------- Declaraba `input` mientras compone `ColorField.Input`, que es `div` + `role='group'` — el contenedor de segmentos, la misma forma que el `input` de date-field. Las piezas editables son las partes hermanas `channel-segment`. Verificado ---------- Guard en verde CON LA LISTA VACIA. Navegador: el trigger de tooltip vuelve a ser texto plano (sin cromado), enfocable, y abre al foco; consola limpia en tooltip y chronos. 2246 tests del uix (los 6 fallos de `contracts.test.ts` siguen siendo de la otra sesion), `check` 70 errores en linea base — una pasada intermedia dejo 71 por un `SomaKeyboardEvent` sin importar en chronos. `docs:check` 0/623. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
ff86971a07 |
fix(morfo,soma,eidos): una flecha, una forma — y `defaultElement` deja de mentir en 24 partes
D12 · las dos flechas sueltas
-----------------------------
`internal/arrow.svelte` dibuja poligono MAS un contorno trazado; context-menu y
link-preview rendian un `<svg>` propio con solo el poligono. Dos de las siete
flechas del sistema no tenian contorno — medido: link-preview no tenia `<path>`
ninguno mientras tooltip si.
Ahora componen la primitiva, asi que las siete son el mismo `<span>` con el mismo
`<svg>` y las siete declaran `span`. Las recetas pasan a direccionar las formas
por dentro (`polygon { fill }`, `path { stroke }` con `non-scaling-stroke`):
un `fill` sobre el envoltorio no puede pisar los atributos de presentacion que
poligono y path llevan encima.
⚠️ El arreglo destapo otro defecto. Al componer la primitiva la flecha
DESAPARECIO: `<span>` de 0x0, sin `<svg>` dentro. Los wrappers de eidos de estos
dos pasaban `{@render children?.()}` INCONDICIONALMENTE, y la primitiva lee
cualquier snippet de children como «el consumidor trae su propio glifo» y se
salta el suyo. popover y tooltip no pasan children en absoluto, por eso nunca les
paso. Los dos son condicionales ahora. Es la regla «eidos wrappers need
conditional children», reaprendida midiendo en vez de leyendo.
Medido despues: los dos dan `<span>` 10x5 con `<svg>` 10x5, poligono relleno y
`<path>` con trazo, identicos a tooltip (el control). Consola limpia.
D11 · 24 de 28
--------------
Eran 28, no 24 — mi cifra anterior estaba mal.
Dieciseis son el morfo yendo detras de un componente que ya renderiza lo mas
correcto (`header`, `p`, `span`, `button`): edicion de declaracion, el DOM no se
mueve.
Ocho parecian del grupo contrario Y NO LO ERAN. `tree-view.item` declara `li` y
rinde `div`, si — pero tambien declara `role='treeitem'`, y lo mismo
`file-upload` con `list`/`listitem`, `feed` con `article`, `stepper.list` con
`tablist`, `tree-view.branch-content` con `group`. La a11y NO esta rota: el rol
explicito carga la semantica que daria el elemento nativo. La declaracion era
aspiracional. Cambiar el DOM a listas nativas es una mejora deliberada aparte,
no algo que colar en un pase de verdad-del-contrato.
Las 4 que quedan piden un cambio con consecuencias, y cada una lo lleva escrito
en `DEFAULT_ELEMENT_DEBT`:
- `chronos.event-chip` — aqui el morfo es el que tiene razon, con el motivo
escrito desde el 2026-08-10: el chip CONTIENE el tirador de redimensionado y
el modelo de contenido de un `<button>` no admite un control anidado. El
componente rinde `<button>` igual. Pero el «tabindex propiedad de soma» que ese
mismo comentario promete NO EXISTE en la parte, asi que cambiar el elemento hoy
canjearia HTML invalido por un control sin foco.
- `tooltip.trigger` — `button` declarado, `div` con `tabindex: 0` y sin rol
rendido. Un boton nativo se trae `type=submit` dentro de formularios.
- `stepper.item` — `div` sin rol dentro de un `role='tablist'`. Problema de
patron, no de declaracion.
- `color-picker.channel-input` — compone `ColorFieldInput`, no renderiza un
elemento.
Verificado: guard de `defaultElement` en verde con las 4 de deuda; 2246 tests del
uix (los 6 fallos de `contracts.test.ts` siguen siendo de la otra sesion);
`check` 70 errores, linea base.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
7caf231b0c |
feat(soma,morfo): Home y End en la familia de segmentos, con una sola accion declarada
APG `spinbutton` dice que Home salta a `aria-valuemin` y End a `aria-valuemax`.
Tres auditorias lo pidieron por separado —date-field F-1, time-field F-2,
color-field F-2— y las tres coinciden. El min/max ya existia por segmento; solo
faltaba la tecla.
Tercera afirmacion falsa de la fila D6: color-field YA LO TENIA
---------------------------------------------------------------
Su F-2 estaba desfasada. `ColorFieldChannelProvider.handleHomeEnd` existe desde
que se construyo. Asi que el pase no invento conducta: copio la referencia que ya
vivia en el arbol a date-field y time-field, incluidos sus `dayPeriod` — donde
`aria-valuemin` es AM y `aria-valuemax` es PM, luego Home/End son ABSOLUTOS
donde las flechas alternan (pulsar Home dos veces no vuelve a voltear).
Es un JUMP, no un ciclo: no envuelve y no lee el valor previo, asi que aterriza
tambien sobre un segmento vacio.
Una sola accion para los tres
-----------------------------
F-1 proponia `set-min`/`set-max`; color-field declaraba `first-item`/`last-item`.
El tipo obliga a elegir — «Semantic action identifier. Keep consistent across
components for the same intent» — y el catalogo tiene DOS vocabularios: el de
LISTA (`first-item`, 25 usos, y `menu-dial` lo consume por el mapa de acciones
del runtime) y el de VALOR (`set-min`, que usa `knob`). Un segmento es un control
de valor y no tiene items. Los tres declaran ahora `set-min`/`set-max`, y
color-field se alineo: sin riesgo, porque los segmentos despachan por tecla, no
por el mapa del runtime.
Medido
------
Teclado real en el navegador, y cada valor cae exactamente en el aria que su
segmento publica: date-field mes End→12 / Home→01 (min/max 1/12), time-field
hora End→23 / Home→00 (0/23), color-field hex End→ffffff / Home→000000
(0/16777215). Home dos veces no envuelve.
Seis tests nuevos y LOS SEIS VISTOS FALLAR contra la conducta vieja: cerrando el
gate compartido de `isAcceptableSegmentKey` y retirando cada rama por separado.
Uno de ellos es para color-field, que era la referencia del pase y no tenia
ninguno — el arbol copiaba de algo que nadie vigilaba.
⚠️ Un tropiezo propio: la primera version del test numerico llamaba a
`handleHomeEnd` directamente, asi que habria seguido verde si alguien borraba el
enrutado del `keydown`. Reescrito para entrar por `onkeydown`, que es lo que
prueba puerta + enrutado + conducta.
`check` 70 errores (linea base; una pasada intermedia dejo 73 por un acceso a
`dayPeriod` sobre el tipo union — estrechado con `DateAndTimeSegmentObj`, el
mismo que usa el provider). 217/217 en los tres campos + morfo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
4ca16847b6 |
docs(soma,process): D5, D6 y D9 medidos — y cuatro afirmaciones del ledger eran falsas
Al ir a arreglarlos, tres de las cinco filas resultaron decir cosas que no se sostienen. Las corrijo con la evidencia delante, porque una fila que miente es peor que una fila vacia. D5 — la doc ya estaba adjudicada; no se propago ----------------------------------------------- La fila decia dos cosas falsas. (1) «modal promete bloquear Escape»: esa promesa ya se habia adjudicado AL REVES el 2026-08-11 en tres pickers, con razon escrita en `time-picker/types.ts:42` — Escape descarta en AMBOS modos porque el morfo declara el patron APG `combobox`, y el framework bloquea Escape en exactamente un sitio, `alertdialog`; tragarselo dejaria a un usuario de teclado sin salida salvo que el consumidor recordara componer un Cancel. La correccion no llego al handle compartido ni a date-picker / date-range-picker. Propagada aqui. (2) «no hay buffer»: si lo hay — `valueOnOpen` se captura en la arista de apertura y `cancel()` lo restaura. Lo que SI es defecto, medido en navegador: en `mode='modal'` el popover no autocierra al elegir, pero Escape cierra Y SE QUEDA la edicion (05/20 → 05/10). Es un tercer camino de salida que se comporta como Save, y el contrato del modo solo tiene dos. La causa esta a la vista: `PickerCloseCause` es `'save' | 'cancel' | 'dismiss'` y **`'dismiss'` no lo produce nadie** — misma clase que D1/D3/D4. Escape cierra por la via del Popover sin pasar por `closeWith`, asi que ni sella causa ni revierte; ningun picker cablea `escapeKeydownBehavior`, luego los cinco se comportan igual. Queda decision: que Escape produzca `dismiss` y que `dismiss` revierta en modal (no en inline). Se deduce de los dos modos documentados, pero cambia el comportamiento de cinco componentes, asi que no lo improviso. D6 — no es una decision sin tomar, es una spec sin construir ------------------------------------------------------------- La fila decia «Home/End/PageUp/PageDown documentados en 4 sitios» y «las tres paginas se contradicen sobre que harian». Ninguna de las dos: PageUp/PageDown no aparecen en ninguna parte, y las tres auditorias COINCIDEN — `Home` → `set-min`, `End` → `set-max`, APG spinbutton, extensivo a date/time/color-field. F-1 llega a escribir la declaracion del morfo literal. El min/max ya existe por segmento (`aria-valuemin`/`aria-valuemax`); falta la tecla en el plan de teclado de los tres morfos y el enrutado en los varios `onkeydown` de cada provider. La auditoria lo dimensiona como pass de familia. D9 — los dos documentos de chronos discrepaban ----------------------------------------------- El README de soma decia que el pack «ships rules» para `handle-drag` y `handle-resize`; el SPEC, que se retiraron el 2026-08-07 por codigo muerto. El SPEC tenia razon — el pack solo lleva los tres `commit-*`. README al dia. Lo que queda es la decision que el SPEC ya deja preparada: o el provider los emite al traducir el drag-drop reutilizado a comandos —y entonces el gesto suena por REPETICION, `step` por emision, como el resto de arrastres— o las dos DECLARACIONES se retiran del morfo. Verificado: `docs:check` 0/623, `check` 70 errores (linea base), 199/199 en date-picker + picker-shell + morfo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
114a2dd633 |
fix(morfo,soma): `defaultElement` era un contrato que no vigilaba nadie
D7 y D8 no eran dos defectos: eran dos instancias de la misma clase. Un censo
del catalogo encontro que `defaultElement` MIENTE en decenas de partes, y es un
contrato — `assertContract`, las recetas y quien lee el morfo lo toman como el
tag que aterriza en el DOM.
D7 · cinco Labels, no dos
-------------------------
El morfo declara `label` y se renderizaba `<div>` en date-field,
date-range-field, time-field, time-range-field y color-picker.
La otra mitad de la fila era FALSA: decia que la doc promete un par `for`/`id`
que nadie emite. No existe tal promesa — `date-field/types.ts:158` dice lo
contrario y bien («Prefer `aria-labelledby` with a `DateField.Label`»). La fila
habia mezclado el hallazgo del `Field` generico, que si emite `for` condicional.
El nombre accesible ya estaba cableado por `aria-labelledby` desde el
`role="group"` (que es el mecanismo correcto: `for=` no puede apuntar a un
grupo) y el clic-a-enfocar ya lo hacia el provider.
Riesgo comprobado antes de tocar: `<label>` es `inline` y `<div>` es `block`.
Dos reciben `display: inline-flex` de `field.css`; los otros tres son items de
flex o grid y el navegador los blockifica. Medido despues en los cinco demos:
ninguno queda `inline`, ninguno arrastra un `for` que no pueda honrar, y la
maquetacion no se mueve.
D8 · los siete arrow, no uno
----------------------------
La sospecha de deuda sistemica era cierta. Los SIETE declaraban `div` y ninguno
renderiza un `div`: cinco componen `internal/arrow.svelte`, cuyo elemento de
parte es un `<span>`, y dos rinden un `<svg>` propio. Cada morfo declara ahora
lo que su componente renderiza.
El guard, y el falso verde que casi cuela
-----------------------------------------
Sin guard esto vuelve a derivar, asi que hay uno en `contracts.test.ts` con las
24 partes restantes enumeradas en `DEFAULT_ELEMENT_DEBT` — visible y sin poder
crecer — mas un detector anti-podredumbre que falla si una entrada de la lista
deja de estar rota.
El primer borrador se fue en FALSO VERDE. Tomaba «el tag del ultimo `{:else}`»,
y el `<svg>` decorativo de `internal/arrow.svelte` no lleva spread: la busqueda
fallaba, el tag volvia `undefined` y una regresion inyectada en `popover.arrow`
PASABA. Se descubrio inyectandola antes de fiarse. La regla correcta es que el
elemento de la parte es EL QUE DIFUNDE LOS PROPS. Con ella el guard encontro 8
violaciones mas que el censo a mano.
Segunda correccion: hay partes polimorficas por diseño — `media-player.media`
rinde `audio` o `video`, `breadcrumb.link` `span` o `a`. El guard lee TODAS las
ramas y da el contrato por bueno si el declarado esta entre ellas; leer solo la
primera las marcaba como mentira. Eso saco a `chat-composer.input` de la lista
de deuda, donde yo lo habia metido por error — lo dijo el propio detector.
Las 24 que quedan (D11) se parten en dos y por eso no van en bloque: unas el
morfo va detras del componente (arreglo mecanico), otras el componente va detras
del morfo con peso de a11y — `tree-view.item` declara `li` y rinde `div`,
`stepper.list` declara `ol`, `tooltip.trigger` declara `button`. Eso es
semantica de lista y de control: son decisiones, no ediciones.
Queda abierto D12, tambien del censo: `internal/arrow.svelte` dibuja poligono
MAS un contorno trazado, y las dos flechas propias solo el poligono. Dos de
siete flechas del sistema no tienen contorno. El arreglo es que compongan la
primitiva compartida, pero es un cambio visual y no se improvisa.
Verificado: 2240 tests del uix en verde (los 6 fallos de `contracts.test.ts` que
se ven en el arbol son de la otra sesion — waveform, media-player, audio-player,
menubar, aura, radio-group, tabs — y el guard nuevo no esta entre ellos);
`check` 70 errores, linea base; `docs:check` 0/623.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
94eb5fce59 |
docs(process): el handoff cierra §2.1 y se queda con la ley, no con el caso
`e5811e422` cerro el `grid` repetido. El handoff seguia diciendo que estaba abierto, y decirlo mal es peor que no decirlo. Se queda ademas la regla general, que es lo que sirve manana: 50 de los 252 destinos partRef del catalogo son partes repetibles, y casi todos estan bien porque su ocurrencia tiene UN sujeto entre muchos — se ancla (`table` con su fila) o se pasa `targetOverride` (el dia del calendario). Solo hace falta declarar un contenedor cuando la ocurrencia tiene TODOS los sujetos a la vez. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
2c555e4be6 |
docs(process): los nueve defectos abiertos salen de mi memoria y entran en el repo
Los defectos que dejo el barrido docs-codigo del 2026-08-11 vivian solo en la
memoria de la sesion que los encontro. Eso es una superficie de recuerdo del
agente, no del proyecto: un handoff que no se carga los pierde, y son la unica
huella de nueve averias que se decidio NO tocar. Ahora tienen casa, con la forma
del ledger de blocks: escritura por `id`, un ARREGLADO lleva su sha, y una fila
de gap no se borra nunca.
D1 · D2 · D3 · D4 arreglados (`f4e414e2e`, `6f42eebfe`). D5…D9 confirmados y
abiertos. D10 es nuevo y nace de un error mio de metodo.
D10 — la migracion a medias
---------------------------
El 2026-08-06 `dragSignalOverrides` paso a nombrar `step` (el gesto suena por
REPETICION) y eso dejo a los tres resolvers de gesto sin un solo llamador. Yo
lei «cero llamadores» y propuse retirarlos. Cinco documentos dicen lo contrario,
y dos de ellos estaban en la salida de mi propio grep sin que los abriera:
`PLAN-sound-engine.md` §4 los pone en la columna «sema conserva» de la LINEA DE
CORTE con `$sound`, y `PLAN-audio-player.md` D-AP.7 los da por medidos con un
check verde. No es codigo muerto: es una migracion que no se adjudico.
De ahi la regla 4 del ledger. «No lo referencia nadie» es una observacion, nunca
un veredicto — la misma leccion que la fila de «Gaps» que dos agentes borraron.
Verificado antes de escribir cada fila, no copiado de la memoria: D2 esta
arreglado y mi registro lo daba por abierto; y la primera lectura de D9 decia
que los tres `commit-*` de chronos tampoco se disparan — falso, se despachan por
variable en `emitOnChip`, invisibles a un grep de `trigger('literal')`.
Y el handoff del plan
---------------------
`PLAN-event-name-normalization.md` no tenia. El eje esta cerrado (256/256 con
prefijo, guard visto fallar, barrido sobre los 172 morfos), pero quedan dos
decisiones tuyas bloqueando trabajo — D10 y el `present-rise` anclado al trigger
— mas tres cosas que el eje destapo sin cerrar: el `grid` repetido con
`numberOfMonths=2`, el nombre `handle-drag-progress`, y los cinco defectos.
`docs:check` 0 errores sobre 623 docs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
e8acbbdf0a |
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se lee manana antes que nada: los cuatro commits pusheados, la unica cadena que sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que hoy esta libre), y por que las otras diez NO son deuda sino clases medidas (estado, interpolacion, composicion, supresion sobre prop sin default). Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon: desbloquearia solo una de las tres clases y reintroduce la averia que esta jornada mato — el guard v2 no recorre los valores del mapa y `normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa saldria literal. Y queda escrita la forma correcta por si alguna vez se hace: valores del mapa como FUENTES, no como cadenas traducidas. Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores (que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea base con la que medirse manana, incluido el matiz de que `check` da 70 con el WIP ajeno presente y 57 sin el. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
61457baac0 |
docs(process): el handoff dice lo que paso, incluido donde el diagnostico viejo mentia
Dos diagnosticos heredados eran falsos y mandaban al sitio equivocado, asi que se corrigen en su sitio en vez de anadir una nota al final: el crash del guard de traducciones no era una url multilinea de un catalogo (era el stripper de comentarios rompiendo un https:// dentro de una cadena) y el guard, ademas, nunca escaneo los call sites que se le atribuian. Dos secciones daban ordenes OPUESTAS a §3.0 y llevaban un dia haciendolo: el hallazgo «chronos estampa fuera del morfo» pedia una decision que C1 ya habia tomado el mismo dia, y «la cola de la campana» listaba como pendientes nueve componentes que las olas 2-3 habian cerrado. Marcadas resuelta y congelada, con los cerrados tachados y remision a la cola viva. Y se escribe lo aprendido en el eje del nombrado: las cinco clases de cadena, las cuatro razones por las que un default NO puede subir al contrato (estado, interpolacion, cross-element, supresion por `<label for>`), la trampa del spread condicional, la del harness cuyo `translate` diverge de `langs.ts`, y la regla que resume la clase — antes de mover un default, mira si el elemento es tuyo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
8864ba93ca |
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
56d7f744e9 |
docs(process): retirado el defecto del `data-event-id` — no reproduce
Lo reporte ayer como defecto de contrato C1 tras verlo faltar en tres chips. Al ir a arreglarlo no aparece bajo ninguna condicion: carga limpia con lectura inmediata y a +50/+200/+1000 ms, re-render de mes, arrastre real que aterriza, y el arrastre muestreado en mitad, en el mismo tick del pointerup, en microtask, en macrotask, a un frame y asentado. 17/17 siempre. La explicacion mas probable —no medida, y por eso no la doy por buena— es estado rancio de HMR: la misma sesion produjo otros dos fantasmas (`DayAdd is not defined` y dos `soma::runtime.part`) que en pestana nueva no existian. Leccion de metodo anotada en el handoff: un hallazgo sobre el DOM en una pestana que ha sobrevivido a varios HMR no vale; repetirlo en pestana nueva ANTES de anotarlo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b7f8899572 |
fix(chronos): una ocurrencia recurrente deja de fingir que se puede arrastrar
Arrastrar una ocurrencia iluminaba la celda de destino y al soltar no pasaba nada: su id es sintetico (`serie::YYYY-MM-DD`) y `moveEventToDay` lo buscaba en `calendar.events`, que solo guarda la SERIE, asi que `find` devolvia undefined y salia en silencio. Iba a resolver el id a la serie —como hace `openEdit`— hasta ver que `canResize` YA excluye las ocurrencias de forma explicita («those edit via the series in v1»). La politica escrita no es desplazar la serie: es bloquear la manipulacion directa y editar por el dialogo. El defecto no era la resolucion del id, era que la afordancia mentia. `canMove(event)` en el provider, espejo de `canResize` y con su razon escrita, y `disabled` en los TRES `DragDrop.Draggable` (mes · all-day · timed). De paso, el handler de teclado gateaba las DOS permisos con `canResize`; ahora cada flecha consulta la suya (Shift → resize, flecha sola → move). Hoy los dos predicados valen lo mismo, asi que en el teclado no cambia la conducta, solo el significado. Medido en navegador: la ocurrencia ya no INICIA el arrastre (ni `data-dragover` ni `data-dragging`), y un evento normal sigue cayendo — Retro 18 jun -> 25 jun. Teclado: flecha sobre la ocurrencia no mueve ni agarra; sobre `Launch` mueve 15 jun -> 16 jun. Queda la mitad cosmetica, anotada: el chip bloqueado sigue con `cursor: grab` y sin `data-disabled`, porque `Draggable` no lo estampa. Pintarlo pide un gancho declarado — decision, no bug. Y el handoff registra por que la rebanada 3 de C5 no es mecanica: casi todos los helpers de la vista de mes los comparten semana y agenda, y varios son COMPORTAMIENTO que pertenece a soma. Partir la vista antes de mover eso repartia la fuga entre cuatro ficheros en vez de arreglarla. check 74 = base · 458 vitest · docs:check 0/621 · rtl:check 0/177 · consola limpia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
fc3a9c0e5a |
feat(eidos): el editor de chronos sube a la raiz, como la paleta
C5 rebanada 2 de 4. El Dialog del editor tenia el mismo hueco que la paleta y se cierra igual: extraido a `chronos-editor.svelte` (Dialog + editorCtx + save/remove) y renderizado por la RAIZ, no por el cuerpo por defecto. Un arbol compuesto que coloque `EditAction` o celdas de dia —partes publicas— ya recibe editor cuando algo llama a `openEdit` / `openCreate`; antes, sencillamente, no tenia ninguno. `chronos-view.svelte` baja de 1199 a 893 lineas y pierde los 7 imports que la extraccion dejo huerfanos. Medido con el discriminador que si distingue de quien es el editor (el portal del Dialog hace que `contains()` no sirva): mover SOLO la instancia compuesta 5 meses y luego elegir desde SU paleta un evento de junio. away -> [junio (defecto), noviembre (compuesta)] elegir «Retro» en la paleta de la COMPUESTA after -> [junio (defecto), junio (compuesta)] la de por defecto NUNCA se movio 1 editor abierto · title «Evento» · EditAction presente El handler corrio en el provider de la instancia compuesta y el editor que aparecio es el suyo. Camino por defecto tambien medido: clic en un chip abre el editor en modo vista, con EditAction traducido, el «when» y el pie. check 74 = base · 458 vitest · docs:check 0/621 · rtl:check 0/177 · consola limpia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
a542f9f10d |
feat(eidos): chronos abre su estructura, y el cuerpo por defecto la estrena
C5 rebanada 1 de 4. Que partes son publicas no es cuestion de gusto: es `kind` en el morfo (21 publicas, 12 privadas). Namespace `Chronos.*` con la forma de Table/Calendar, y la raiz gana ranura de composicion — pasar `children` REEMPLAZA el cuerpo por defecto. Antes se renderizaban ADEMAS de la vista completa, un segundo arbol colgando de un calendario entero; ningun consumidor los pasaba. Ocho partes de toolbar (`Toolbar`, `Heading`, `Prev`/`Next`/`Today`/`Undo`/ `Redo`/`SearchButton`), ergonomicas al estilo `Calendar.PrevButton`: componen el IconButton/Button del sistema, su glifo y la llamada al provider, asi que el consumidor recibe un control que FUNCIONA y no un gancho vacio. El cuerpo por defecto se recompone desde ellas — no es un camino privilegiado, es el primer consumidor de la API compuesta. `searchOpen` sube al provider: en un toolbar compuesto el boton y la paleta viven en subarboles distintos y el provider es el unico sitio que alcanzan los dos. Y la prueba compuesta destapo un hueco real — el SearchButton compuesto no abria nada porque la paleta vivia en el cuerpo por defecto. Los overlays pertenecen a la RAIZ: extraida a `chronos-search-palette.svelte`. El Dialog del editor tiene el mismo hueco y sigue dentro de la vista; es lo primero de la rebanada 2. Dos declaraciones falsas mas, del patron de siempre: `toolbar` declaraba `header` y se pintaba un `div`; `heading` declaraba `div` y se pintaba un `span`. Los wrappers renderizan lo DECLARADO, medido sin desplazamiento. Medido con dos instancias en la misma pagina: next en la compuesta mueve solo la compuesta (junio -> julio), prev en la de por defecto mueve solo esa (junio -> mayo), ids de heading distintos, y la paleta de cada raiz lista sus 18 eventos por separado. check 74 = base · 458 vitest · morfo:check PASS chronos · docs:check 0/621 · rtl:check 0/177 · eidos-lint invalid 0 · consola limpia en las tres vistas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
968fff9cdf |
feat(morfo,soma,eidos): chronos deja de tener partes que el morfo no conoce
C3 cierra con `more-link` y C4 con los seis marcadores que eidos estilizaba sin declaracion. Los tres botones sin fuentes (`day-add`, `edit-action`, `search-button`) comparten una clase; `undo`/`redo` se cablean sobre la del nav; `timegrid` pasa a `time-view` porque estaba a un guion de la parte `time-grid` que vive dentro; `peek-panel` y `day-peek` quedan como partes de display puro con marcador inline. Cada una renderiza un `<button>` real cuando el morfo lo declara: una `Region` que cae a `<div>` pondria el `type` declarado sobre un no-boton. La lupa deja de ser un adorno sin `onclick`: abre una paleta `Command.Dialog` sobre TODOS los eventos, no un filtro de la vista, con `goToDate` nuevo en el provider — la generalizacion de la que `goToday` era un caso particular. El texto buscable viaja en `keywords` porque el scorer de Command lee value + keywords y NUNCA los hijos renderizados, asi que se busca por titulo, por tipo y por fecha. Y tres etiquetas que mentian en silencio: `translationRef` / `commonRef` normalizan la clave LITERAL y no consultan ningun mapa, asi que `prevMonth` / `nextMonth` / `moreEvents` y `action.undo` / `action.redo` caian al fallback ingles en una pagina `es`. Corregidas contra el catalogo, con `common.buttons.undo` / `redo` anadidas. Verificado: check 74 = base (delta cero) · 458 vitest · docs:check 0/621 · rtl:check 0/177 · eidos-lint chronos morfo-backed 32 -> 44, invalid 0 · navegador limpio en las tres vistas, con el A/B del undo medido (18 jun -> drag -> 25 jun -> undo -> 18 jun -> redo -> 25 jun) y el salto de la paleta (noviembre -> elegir Retro -> junio). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
56a96114f8 |
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
El defecto que tres auditorías encontraron y ninguna cerró no era que N eventos redirigieran sin declararlo: era que la API de emisión aceptaba un HTMLElement suelto, compensando que el registro de partes perdía la identidad de instancia. Censar y arreglar a mano reproduce el modo de fallo. Esto lo hace inexpresable. F1 — SomaRuntime<M> genérico, SIN default. Nombres de evento, kebabs de parte y claves de events/actions tipados contra la declaración (EventNameOf / PartKebabOf / ActionNameOf, los extractores que semaSelector ya usaba del otro lado). 104 anotaciones migradas + 3 wrappers que devolvían tipado pero tomaban sources ANCHO. SomaRuntime<Morfo> queda sólo en la tabla de contratos. F2 — registro por instancia: Map<part, PartRegistration[]>, pertenencia mantenida por el stream del attachment (poda al desmontar, re-entrada al remontar), resolución liveReg = la instancia VIVA más nueva. Mata de paso la clase "la más nueva muerta ensombrece a una viva". F3 — tres formas legales de destino: el target declarado; el anclaje por instancia (SomaRuntimePart.trigger, tipado por EventNameTargeting, + runtime.partInstance por identidad de referencia); y targetFallback, la cadena por estado de montaje declarada en el morfo y resuelta por resolveEmitTarget — LA resolución única para validación, emit y foco a11y, que ya no pueden discrepar. ~30 componentes migrados; censo de targetOverride 148 -> 89. F4 — A-36 tenía un superviviente. Medido en dropdown-menu: bajo `pre` el `open` no colisionaba con el contact-activate del Button compuesto, se PERDÍA entero porque su content no había montado. Aplicada la receta de float-panel a dropdown-menu Y context-menu. Ahora: contact-activate en trigger, open en content — dos superficies, ambas expresando. Submenús mudos, tres componentes. No era cableado: el morfo no declaraba el evento. onion-menu recibe emerge-expand/emerge-collapse (la forma de TreeView: un nivel revela sus hijos en el sitio); dropdown-menu y context-menu reciben sub-open/sub-close sobre sub-content, distintos del open raíz a propósito porque el sub es su propia superficie flotante. Nacen con la receta A-36 para no repetirla en código nuevo. chronos — declaraba 26 partes y registraba UNA; 17 role= a mano y el ARIA declarado no llegaba nunca al DOM. Se anula la mitad sobre-extendida de la decisión de 29ffe902a: la reutilización sigue viajando por el motor puro, pero la ESTRUCTURA se compone como en Table y Calendar. C1 (las 6 partes que emiten, chronos con cero targetOverride) + C2 (8 singletons) + C3 8/9. Registrar una parte DESTAPA declaraciones falsas, tres veces en un pase: el chip declaraba defaultElement button y ponía type= sobre un div (anida el asa de resize); grid-row declaraba archetype item y pintó la semana entera como fila de lista; y un aria por propRef desaparece en SILENCIO si el provider no publica la fuente. En los tres el arreglo fue la DECLARACIÓN, no el DOM. Verificado: check 74 = línea base medida con stash, DELTA CERO sostenido en cada lote · 458 pasan (chronos+morfo+sema) · navegador limpio en las tres vistas de chronos, con el contrato ARIA vivo por primera vez (aria-labelledby resolviendo a «junio de 2026», aria-readonly/disabled que no se habían emitido nunca). Handoff en docs/process/CONTINUE-perceptual-surface.md. Aparte, y decidido por el autor: docs/process/PLAN-event-name-normalization.md — prefijo de familia siempre; 36 de 252 sin prefijo, cero ambiguos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b1b48fb3aa |
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie podia distinguir una redireccion legitima de una deriva, porque la opcion que redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia. EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64 ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo por diseño. LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba `data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a ser el suelo correcto. A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms, con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el trigger, press-squeeze jamas. `regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`, este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0 huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no se anuncia»), con sus 4 declaraciones muertas. El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay animaciones, asi que awaitExpression volvia al instante. El navegador midio el commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion. Medir la envolvente no es medir la salida. `renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform, aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement. Ademas: 5 selectores muertos en el predicado del focus-scope de palabras (barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome (inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union inline al `Direction` canonico, que destapo que el censo de direccion grepea el NOMBRE del tipo · deuda de chronos (scope, barrel, README). Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2 (el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite). Todos los guards nuevos vistos fallar antes de arreglar nada. Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno, reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177. Handoff: docs/process/CONTINUE-perceptual-surface.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b345aa5317 |
docs(process): el handoff de blocks avisa de que el sonido cambio de modelo
soundTuning/SOUND_TUNINGS/sound() estan retirados con guard propio: el sonido ya no se modula, se ELIGE, y lo decide el mapa por verbo — un pack escribe regla solo cuando difiere del default. Nada de la cola se invalida (lo pendiente es que evento se emite y sobre que parte), pero el metodo cambia: si hace falta tocar el sonido, se NOMBRA. Y el limite de la sonda, que el handoff de sema documenta como leccion cara: contar nodos de audio sirve para saber si emite, no para saber como suena — eso exige render offline y medir el pico. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c16ad18bc3 |
docs(process): el handoff deja de decir que esta pusheado y que el servicio es uno
Dos frases que quedaron mintiendo tras la actualizacion: el push a gita (esta en alpha-0.1-dir-prefs, sin push) y la convencion de servicios (son dos desde hoy, traductor y anunciador). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
05eb11f2b8 |
docs(process): el handoff de blocks cuenta lo que hay, no lo que habia
El documento decia 29 confirmados por arreglar y 50 sin verificar — justo lo que
la re-verificacion invalido. Ahora apunta al ledger como fuente viva, recoge el
hallazgo grande (8 de las 12 ALTA de percepcion eran del canon), lo hecho en
|
2 months ago |
|
|
e6a445c453 |
docs: el corpus entero deja de describir un sistema que ya no existe
Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
fb7759b849 |
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian
F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se
derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que
las origino sigue siendo cierta; lo que cambia es quien la sostiene.
D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera
base.sound, porque sound() devolvia una firma COMPLETA y la cascada la
aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz
ascendente. S-07 era el mismo defecto una capa mas abajo — el gain
desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision
del autor.
Con el nombre aplicado ANTES de los deltas de intent no queda ninguna
capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no
esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque
SOUND_TUNINGS ya no existe.
LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma
valida (SILENT), el criterio de cuando crear un pack, y los samples como
recurso de producto (el catalogo los admite con nombre; la app autora los
suyos por overrides.cascade, que sigue abierto a proposito).
LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en
tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No
hay playbackRate ni detune. Sobre un .wav el intent solo mueve el
volumen — medido, risk es indistinguible de neutral porque solo aporta
roughness y la ruta de sample lo ignora. Un sample con carga evaluativa
necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente
a error y la verdadera razon de que fuese la excepcion. Es fisica del
audio, no politica.
Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue
(reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema
doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6,
que es el handoff vivo y estaba describiendo una cola que ya no existe.
⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO
estaban formateados en HEAD; reaplicado a mano para que el diff sea solo
lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2
lineas en vez de 30.
VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check
0/618 · prettier limpio en los ficheros que toque.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
7154fb77ef |
docs(process): PLAN — el sonido se NOMBRA, un nombre y punto
Mandato del autor: «yo solo quiero en la semantica en sonido darle el nombre de un sonido y punto, y me da igual que sea generado o sea un wav; todo lo demas es sobreingenieria». No es una simplificacion de conveniencia: restaura la ley que sema.md:390 ya tenia escrita — «components reference names; URLs and parameters live in one place»— y que nada vigilaba. LA MEDIDA que lo justifica, en runtime sobre los 71 packs: 214 reglas autoran 33 firmas distintas, y las 8 primeras cubren el 76%. Dos firmas —gain 0.05 y gain 0.03— explican 70 reglas ellas solas. El 43% de la superficie perceptual del framework es un componente diciendo «mas bajito». Ejes tocados: gain 165, contour 69, pitch 67, roughness 23 — contour/pitch/roughness son del INTENT, y contour es un enum, luego 69 reglas borran el contorno que el intent acaba de poner y no pueden hacer otra cosa. El guard que naci ayer para proteger el intent no mira contour: esta ciego en el unico eje incomponible. EL DISEÑO: un catalogo de nombres (fusion de SOUND_LIBRARY y SOUND_TUNINGS), el tipo de la regla estrechado a nombre|SILENT, y el nombre aplicado ANTES de los deltas de intent. Con el nombre debajo, D.7 y S-07 dejan de PODER existir — no se arreglan, se disuelven. §7.1 registra un error mio corregido por el autor: la primera redaccion abria con una instantanea del estado actual, declarada innegociable. Un A/B necesita una referencia que merezca conservarse y la salida de hoy no lo es; ademas git YA es esa instantanea y el compilador es mejor guard. La instantanea se movio al CATALOGO, que es donde viven las decisiones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
866089a407 |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar
La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c46a955a0f |
docs(process): el informe de la auditoria de sema — 44 hallazgos con evidencia
`AUDIT-sema-2026-08-05.md`, la fuente de verdad que `CONTINUE-sema-audit.md` referencia y que se habia quedado sin commitear. 8 frentes en paralelo (samples · motor · cascada · tipos · packs · morfo↔sema · canales · doctrina), cada hallazgo sometido despues a un agente adversarial que intentaba refutarlo leyendo el codigo: 53 agentes, 1.790 llamadas a herramientas, 44 confirmados y 1 refutado. Cada entrada lleva fichero, linea, la evidencia literal, el impacto y el arreglo propuesto (sin aplicar). Incluye tambien la seccion de lo que esta BIEN y no hay que tocar, que es la mitad que un informe de auditoria suele omitir: los cinco .wav son PCM valido, la ruta de sample se recorre de punta a punta, los 248 eventos de los 166 morfos cumplen el vocabulario cerrado, el orden de capas es el documentado y la fuga de nivel entre reproducciones esta realmente cerrada. Es cronica: registra lo hallado el 2026-08-05 y no se reescribe segun se van arreglando los hallazgos — el estado vivo esta en el handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
9944d76ede |
docs(process): handoff del hilo de sema — lo cerrado, la cola y como medirlo
`CONTINUE-sema-audit.md`, autosuficiente para arrancar una sesion nueva sin releer las 24 000 lineas del corpus. Registra: - Las seis entregas de esta sesion con su commit, y por que cada una fue necesaria (el silencio como valor, el tremolo en serie, el saneamiento documental, contact-press, D.7 sin versos sueltos, y la regla de cascada que dejaba de alcanzar ancestros). - Las decisiones FIRMADAS que nadie debe re-litigar: `contact-press` (deroga el anexo Tabla 1, con la reversion escrita en el test), la enmienda de D.7 por su motivo —familias sin `base.sound` fuera de la prohibicion— y la reescritura de D.8 —`activeChannels` es default, no prohibicion—. - La COLA en orden de dano, con su evidencia: las 17 reglas de 212 que no disparan o disparan mal (textarea 6x/13x, navigation-menu, gradient-picker, chronos, tree-view mudo por teclado, 5 inertes, 10 con tuning de familia ajena) · los silenciosos (`preload` colgado que ademas abre contexto en el boot, `masterGain` que no hace nada, fallback de sample sin diagnostico, 3 WAV muertos, la rama de tipos que deja pasar un `gain` string) · el interruptor Sound de las demos · el `announce` que no compila · la instantanea perceptual · la recalibracion tras el fix del AM · y los dos ejes (el `contact` ausente en 84 de 91 morfos, `Toolbar.Button` sin componer). - **S-07 marcado como NO TOCAR** sin reabrirlo: decision explicita del usuario. - Como se verifica audio en este repo, que es donde mas tiempo se perdio: render offline replicando el grafo, A/B dentro del MISMO componente, y contar osciladores solo responde «llego algo al motor», nunca «se oye». - Las lecciones de metodo que costaron caro, incluida la propia: medir la envolvente NO es medir la salida, y por eso declare un silencio que a −13.9 dBFS era lo mas fuerte de la interfaz. Baseline al cierre: check 75 · docs:check 0/617 · sema+morfo+sound 369/369. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
2870874cbb |
docs(opts): la auditoria exhaustiva del corpus — el canon deja de mentir
Barrido mecanico sobre los 673 .md del repo (1485 bloques de codigo en 319
docs): cada import contra la tabla de alias real, cada *Provider/*Opts contra
11481 simbolos del codigo, cada tabla de props de los 284 READMEs contra su
types.ts.
Causado por el eje de opts:
- active-architecture §8 (canon) daba OptsFromProps SIN el 4o parametro —el que
salva el `undefined`— y bindProps en forma v1; partOpts no aparecia pese a
ser el mecanismo de 288 wrappers. Tabla reescrita + las dos notas que la
tabla no puede llevar.
- architecture/soma.md tenia el UNICO bloque con bolsa manual que quedaba en
los 673, y sus DOS imports eran rutas fantasma ('../../reactive',
'../../props'). Ejemplo reescrito al canon.
- CONTINUE-direction afirmaba que switch no podia usar OptsFromProps y que
bindProps no acepta Active: falso por ambas mitades desde el eje. Marcado.
- CONTINUE-opts-canonicas se leia como plan VIVO con seis decisiones sin tomar
—una sesion futura habria rehecho 353 ficheros—: banner de cierre con lo que
cada decision acabo siendo.
- El invariante de catalogo sube al estrato arquitectura y gana filas de
aceptacion E-3.7 / E-3.8. drawer documenta que el snap SOBREVIVE al cierre;
knob y cropper precisan que su callback cubre reset y clamp.
Preexistentes que solo aparecen con el metodo exhaustivo:
- carousel documentaba `value`/`onValueChange`; son `index`/`onIndexChange` —y
el mismo README ya decia `index` doce lineas mas abajo.
- navigation-menu seguia con `skipDelayDuration`, renombrado a `groupSkipDelay`
en la pasada N6 (el propio types.ts registra el rename).
- textarea documentaba `submitShortcut` con **el default invertido**: el prop
es `submitOn` y por defecto es `false`, no 'mod+enter' —un consumidor
esperaba que Ctrl+Enter enviara de fabrica—, y `minLength` no existe.
- search-field ensenaba a envolver el callback en un debounce a mano (con el
alias `$lib`, retirado del proyecto) teniendo `debounceMs` propio: era
debounce sobre debounce.
- GESTURES nombraba SplitterTriggerProvider; la clase es
SplitterResizeTriggerProvider.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
30e2a027fe |
docs(opts): handoff del eje de opts canonicas — el plan de manana
La forma de las opts es la ultima artesania masiva del catalogo: 2286 readableActive + 721 writableActive a mano en 593 wrappers, 524 interfaces con 93 re-declaraciones de id/ref, y dos helpers cuya adopcion es 3/101 CON cast en el 100% de los adoptantes. El handoff lleva el censo re-medido HOY (no el del agente de ayer), los 4 defectos de opts.ts con linea, las dos convenciones en guerra, las SEIS decisiones que abren el eje (con recomendacion y sin firmar), los movimientos M1-M4 con el orden (M1 primero: no depende de ninguna decision), la linea base exacta post-endgame (check=75) y las trampas pagadas del eje hermano. Deja claro lo que NO se toca: el mecanismo de dir ya es del morfo y la clase base Provider se retiro a proposito. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
6c65caf3f8 |
feat(direction): el censo cierra — picker, gradient-picker y chat-log aceptan dir
Los tres ultimos del catalogo sin la prop, pospuestos en D4 y firmados hoy:
- `picker` (el generico transaccional): `dir?: Direction` publico; el wrapper
corre `activeDir(() => dir)` en vez del `() => undefined` que solo seguia al
ancestro. `PickerOpts` gana la opt y el TIPO arrastra a sus dos composers
(gradient-picker y natural-time-picker pasan su afirmacion) — el generico
condicional de P4 haciendo exactamente su trabajo.
- `gradient-picker`: prop + cadena + morfo `direction: {}` + runtime.
- `chat-log`: prop + cadena + morfo, y reenvia la afirmacion a los
`FeedProvider`/`VirtualListProvider` que compone en triple registro — que la
exigian en OPTS desde la normalizacion y nadie les pasaba nada. **Sus 2
errores de linea base MUEREN: check pasa de 77 a 75.** Solo la bolsa del
chat-log se spreadea en el elemento compartido, asi que el estampado es suyo
(morfo direction) y Feed/VirtualList reciben el valor para su matematica.
El censo-guard (direction-census.test.ts) los cuenta sin excepcion nueva: prop
y morfo declaran el mismo hecho en los tres. Demos: gradient-picker y chat-log
tenian el control del arnes sin cablear — ahora pasan `{dir}`.
Verificado en Chrome, pagina en LTR y direccion por el chip de la demo:
gradient-picker root dir="rtl" · panel portalizado rtl · 0 islas
chat-log auto -> atributo AUSENTE (hereda) · prop rtl -> estampa rtl
picker via sus composers (no tiene demo propia)
check 75 = 77 - 2 (los de chat-log, desviacion explicada: la base MEJORA) ·
censo + compile + suites de los ambitos 73/73 · rtl:check 1 (palabras) ·
docs:check 0/566. El handoff marca la cola D4 como CERRADA.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
f516b29e90 |
docs(direction): la cola del censo estaba inflada — quedan tres, no seis
grid-list, tag-group, drawer, virtual-grid y virtual-list YA aceptan dir (la ola 9.16-9.19); el handoff arrastraba el censo rancio de 9.15. Verificado por grep de types.ts. Quedan: gradient-picker, picker y chat-log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
8d1909b166 |
feat(direction): eidos comparte el contexto, y el corpus registra el endgame
La entrada eidos se parte igual que la de soma: `activeEidosDir(dir)` devuelve la AFIRMACION (prop -> ancestro) y publica en el MISMO DirectionContext — un chart eidos-only dentro de un subarbol soma afirmado (o al reves) resuelve el mismo hecho. La cola matematica va a `resolveEidosDir(dir, eidos.prefs)`, con la vista de prefs cacheada por instancia (WeakMap): construirla dentro de una funcion pura llamada por $derived era una alocacion por pasada. createChartRtl consume las dos mitades por su lado: `attr` estampa la afirmacion cruda (ausente hereda del <html> proyectado), `current`/`anchor` resuelven la cola. Corpus: - direction-contract.md §2 pasa del campo obligatorio de 4.1 al mecanismo del morfo (declaracion -> tipo condicional -> estampado; secundarios del mismo morfo; el censo como guard), §7 documenta el contexto compartido y la tabla de entradas partidas, y el checklist §8.3 pide morfo + cable. - docs/decisions.md registra la ratificacion D1-D4 del 2026-08-05 con las cuatro decisiones y su porque. - CONTINUE-direction-runtime.md §11 cierra el handoff: tabla de fases con commits, verificacion final medida, la cola pospuesta por D4 y las trampas nuevas (pathspec SIEMPRE en rama compartida; el detector de huerfanos y la ruta del import; la vista en $derived). check 77 = linea base · morfo+direction suites 131/131 · docs:check 0/566. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
1d442c59dc |
fix(media-player): `c` ya no descarta el idioma elegido, y el menu deja de robarle teclas al player
Cierra los cuatro que la revision adversarial dejo vivos y que dependian del
provider. La otra sesion commiteo su refactor (
|
2 months ago |
|
|
482cea3bee |
docs(media-player): las cuatro variantes de audio estan aprobadas, no dos
row e inline las dio por buenas el usuario y yo las seguia listando como pendientes de su mirada. El eje visual del modo audio queda CERRADO. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
35ef18ed02 |
docs(media-player): los cuatro arreglos que bloquea el refactor en vuelo
La revision adversarial dejo cuatro hallazgos ciertos que viven todos en media-player-provider.svelte.ts, que la otra sesion tiene modificado sin commitear. Tocarlo significaria llevarme su trabajo en mi commit, asi que quedan escritos con el arreglo concreto de cada uno. Incluye una observacion sobre SU arbol que no he tocado: con el refactor en vuelo el player deja de estampar el atributo dir crudo aunque la prop lo afirme. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b6caaaa178 |
docs(media-player): el handoff recoge el selector de pista y lo que NO pude verificar
Anade las entregas 5 y 6 (CaptionFloat y la especificacion de la calidad) y, mas importante, una seccion nueva de limites. El panel de navegador embebido dejo de componer frames a media tarde (`document.visibilityState === 'hidden'`) y con el dejaron de llegar las teclas. Llegue a creer que habia un defecto —«Escape no cierra el float dentro del player»— y era FALSO: instrumente un listener y una pulsacion real registro cero eventos. Lo roto era mi via de entrada, no el componente. Queda escrito porque es la misma familia de trampa que las transiciones CSS congeladas, y porque la proxima sesion no debe salir a cazar ese fantasma. Lo que si quedo verificado con eventos despachados: keydown de Enter/Espacio abre el menu, y el click que el navegador sintetiza detras no toca los subtitulos — el preventDefault() de la costura aguanta. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d3c8fcfd37 |
docs(direction): los charts ya no estan en la cola — la linea llevaba rancia
§10.2 seguia diciendo que la familia chart "no tiene prop ni provider y resuelve
leyendo getComputedStyle(node).direction". Eso lo cerro 43a788083: los cinco que
necesitan direccion declaran dir?: Direction y resuelven por
createChartRtl(eidos, () => dir) -> activeEidosDir. Verificado por grep que no
queda un solo getComputedStyle(...).direction en la familia.
La linea me indujo a repetirlo. El registro historico de §9.20 se queda como
estaba — describe lo que era cierto entonces —; lo corregido es lo que apunta
hacia adelante.
Re-hace el cambio que
|
2 months ago |
|
|
6f0f438f30 |
docs(media-player): el handoff recoge las cuatro entregas de la tarde
Waveform visible, velocidad bajable, API de eventos y transporte de lista. Con la leccion reutilizable: componer un primitivo desde soma deja su receta fuera, la carga el componente que lo compone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
64df3d7b37 |
feat(direction): la propagacion pertenece a la capa flotante
Un panel portalizado se renderiza FUERA del subarbol de su dueño, asi que la
herencia no lo alcanza: la afirmacion del dueño solo llega si alguien la lleva.
Eso estaba delegado en cada consumidor — siete envoltorios Content componian el
enlace al padre a mano — y quien COMPONE un popover no tenia por donde hacerlo.
Medido con <DatePicker dir="rtl"> sobre una pagina LTR: el wrapper flotante
estampaba dir="ltr", el picker-shell y su pie se disponian en LTR, y solo el
calendario espejaba. Panel partido por la mitad, el fallo del §2 del contrato.
La capa lo compone ahora una sola vez:
el dir propio de la superficie -> el dir afirmado por el proveedor DUEÑO -> omitir
- FloatingProviderOpts.dir el dueño baja su afirmacion (ya pasada por
activeDir, o sea prop -> prefs)
- FloatingContent.assertedDir donde se encuentran; lo leen los DOS estampados
del wrapper (rama nativa y rama JS)
- createFloatingShellRoot lo reenvia; es la puerta de 8 de los 10
FloatingProvider. Los 2 submenus toman el del
menu padre.
FloatingContentOpts.dir pasa a ser la prop CRUDA del Content. Resolverla ahi la
haria casi siempre concreta (prefs) y el dueño no ganaria nunca el fallback.
Se borran los 7 enlaces compuestos a mano con su soma / activeDir / parentRoot.
Sobrevive uno a proposito: dropdown-menu-sub-content lo necesita para
effectiveSide, que es matematica y quiere valor resuelto.
popover, tooltip y link-preview aceptan dir en su raiz. No estampan nada — no
renderizan elemento —; el valor existe para cruzar el portal, y es lo que
permite que los 9 componentes que componen un popover empujen su direccion al
panel.
Borrados 9 resolvedDir muertos en los proveedores Content: nadie los leia (toda
la matematica direccional consume el de la RAIZ) y el cambio de semantica de
opts.dir dejaba su docblock mintiendo.
Demos: las 7 que aceptan dir lo pasan ahora a la instancia — time-picker,
time-range-picker y color-picker no tenian control ninguno; emoji-picker y
natural-time-picker lo tenian solo en el arnes. Sin eso el camino de la PROP,
el unico que ejercita el portal, no era observable.
Verificado en Chrome real, direccion movida por el control de la demo:
date-picker Clear/Cancel/Close 15/102/209 -> 200/92/15 espejo exacto
date-range-picker 448/217/17
sin regresion dropdown-menu (raiz y submenu, data-side=left en RTL),
context-menu, menubar, select, sidebar (flyout), y tooltip
en auto sigue estampando ltr — la cola de prefs sobrevive
check 78 = linea base (A/B con stash) · 158/158 en los 20 ambitos tocados ·
rtl:check 1 (palabras, preexistente) · docs:check 0/565.
Queda documentado en el handoff un defecto PREEXISTENTE de otra familia que
esto vuelve visible: un componente eidos que monta otro componente del canon en
SITIO no le reenvia el dir, y el hijo estampa ltr cortando la herencia del
panel (natural-time-picker/Slider, emoji-picker/Command+ToggleGroup,
color-field/Select). No hay precedente de ese reenvio en eidos: es doctrina
nueva y movimiento propio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
7b0556a955 |
docs(direction): handoff del refactor — la direccion deja de ser una convencion
El usuario preguntó si lo entregado era lo mas logico y profesional. No lo es:
el contrato es correcto y el MECANISMO para cumplirlo es teclear la misma linea
en cada componente. 54 estampados a mano, 7 enlaces al padre a mano, 9 Content
que re-arrancan la cadena por su cuenta, y 20 de 55 componentes que lo
incumplian hasta `21055bd3c`. Una convencion que hay que repetir 54 veces y que
20 de 55 incumplian no es una convencion — es una abstraccion que falta.
`docs/process/CONTINUE-direction-runtime.md`, para abrir en sesion nueva. Lleva
LAS DOS COMPROBACIONES DE VIABILIDAD YA HECHAS, que es lo que separa un handoff
util de una idea:
- El runtime YA omite un atributo cuyo valor resuelve a `undefined`
(`runtime.svelte.ts:519-522`). Es exactamente la semantica del estampado
crudo. No hay que construir nada para eso.
- El runtime YA inyecta atributos sin declaracion en morfo — `id`, el marcador,
`data-archetype`, `reg.attachment` (`:538-551`), con el comentario que lo
justifica. Asi que el movimiento NO exige tocar 55 ficheros de morfo.
- La capa flotante tiene acceso al dueño: los sub-proveedores llevan
`this.provider`. Y el reparto de los 11 consumidores es el diagnostico —
8 pasan el `dir` DEL PROPIO Content (la fuga), 3 el de la raiz (lo correcto).
Dos movimientos, con el orden razonado (la propagacion primero: mas acotada,
verificable de un vistazo, y BORRA codigo, asi que el segundo llega a un arbol
mas limpio). Dos vehiculos posibles para el primero, sin decidir a proposito.
Lleva ademas la linea base exacta de verificacion y las trampas ya pagadas: el
crudo nunca resuelto, `dir` en un `<svg>` no hace nada, `OptsFromProps` quita el
`undefined`, clasificar por grep se equivoca (me paso con `avatar`), y una
migracion mecanica hereda los defectos que traduce.
⚠️ Anota lo que el refactor NO debe romper: los 4 que correctamente NO estampan
(popover, tooltip, link-preview, context-menu) y la excepcion firmada por el
usuario de `avatar`/`image`, que no aceptan `dir` a proposito.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
64043ae2bd |
fix(media-player): una flecha hacia el slider disparaba DOS acciones
Con el pulgar del volumen enfocado, ArrowRight subia el volumen Y ademas saltaba el playhead un seekStep. Con el pulgar del scrubber, ArrowUp scrubbeaba Y cambiaba el volumen. El Slider hace preventDefault de las cuatro flechas pero no detiene la propagacion, y la guarda de hotkeys del player solo eximia INPUT/TEXTAREA/contentEditable — asi que las dos manos actuaban sobre la misma pulsacion. Arreglado en la guarda del player, no en el Slider: la exencion es por PROPIEDAD, no por etiqueta. Un control que se declara slider maneja sus propias teclas, y el player deja de reclamarlas cuando la pulsacion nace dentro de un [data-slider]. Tocar el Slider habria afectado a todos sus consumidores. CORRIJO UNA AFIRMACION MIA DE ESTA MISMA JORNADA. Al verificar el teclado direccional escribi que raiz y pulgar estaban de acuerdo, un solo seekStep sin doble salto. Era falso: ambos escriben currentTime, asi que el seekBy del player PISABA el paso mas pequeno del Slider y el doble manejo quedaba enmascarado. Se destapo cruzando ejes — ArrowUp sobre el scrubber movia tiempo Y volumen, que son magnitudes distintas. La leccion queda escrita en el handoff: dos manejadores que escriben la MISMA magnitud son indistinguibles. Medido en Chrome antes y despues. Antes: volumen+ArrowRight daba dVol=+0.01 y dTiempo=+8. Ahora: dVol=+0.01, dTiempo=0; scrubber+ArrowUp dTiempo=+0.1, dVol=0; y la raiz conserva sus hotkeys (ArrowRight busca, ArrowUp sube volumen). Guard nacido en rojo por mutacion. player 14/14 · docs:check 0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
bd7853b59e |
docs(sound-engine): mutear antes de reproducir, y parar al terminar
Medir el escenario mixto y los earcons exige reproducir de verdad, pero el Chrome que suena es el del usuario. Dejar una pista de 6 minutos a volumen 1 mientras se hacen otras cosas es una molestia real, y paso hoy. Casi todo lo que se mide —contextos, stamps, envolventes, geometria— no depende de que se oiga, asi que muted=true basta; el volumen solo hace falta para la escucha, que es del usuario. Y pause() al acabar cada medicion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
6de53519d1 |
fix(audio-player): el track de la barra era un punto — el suelo estaba anulado
El usuario lo vio: en la variante `bar` el scrubber quedaba reducido a un pulgar entre 0:00 y 6:12, mientras el volumen conservaba sus 64px al lado. La causa era de una linea. media-player.css le da a todo scrubber un suelo (min-width: --space-8) y la regla de `bar` lo anulaba EXPLICITAMENTE con min-inline-size: 0 y mayor especificidad, asi que flex podia encogerlo hasta la nada. Un control que desaparece en silencio es peor que una fila que admite que no tiene sitio. Dos decisiones de diseno del usuario, aplicadas: - El scrubber nunca se dibuja mas estrecho que el volumen. El ancho del volumen sale a token (--_mp-volume-width) y el scrubber de `bar` se apoya en el como suelo, para que no puedan divergir cuando alguien retoque uno. - El artista se oculta en `bar`; `card` y `row` lo conservan, que tienen sitio. Medido tras el arreglo (variante verificada en la misma pasada): scrubber 64 a 560/720px contra volumen 64, y 229/529 a 900/1200px. Sin desborde en ninguno. Visto en Chrome: los dos sliders se leen del mismo tamano y el titulo gana el espacio del artista. Queda vivo y declarado: el mute-button se estruja a 20-25px bajo 800px. No se toca porque ponerle flex:none empuja el problema a otro control, y esa es una decision de diseno. check 0 errores propios · player 14/14 · eidos-lint invalid 0 · docs:check 0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
92c1ee91a9 |
docs(sound-engine): bar solo colapsa bajo 800px, y la mezcla la elige el disenador
El barrido de anchos reencuadra el defecto: bar esta sano de 900px en adelante (scrubber 147/347/1047) y solo colapsa a 720px, que es el ancho al que la demo encierra al player, no un ancho propio de una barra de app persistente. Corrige ademas la pregunta que yo habia planteado. El usuario zanjo que la mezcla es composicion y la elige el disenador, lo que encaja con la doctrina que audio-player.css ya declara (un variant decide que se muestra y como fluye, nunca comportamiento) y con la salida que ya existe: componer MediaPlayer media=audio a mano mantiene el flujo sin opinion, porque el CSS de variantes cuelga de data-variant y eso solo lo estampa AudioPlayer. Lo que no se disuelve en composicion es que el preset colapse EN SILENCIO. El arreglo pendiente no arbitra entre scrubber y volumen: min-inline-size en el scrubber para que nunca llegue a 0, y el ancho minimo documentado. Y registra dos veredictos del usuario: waveform F6 APROBADA (cierra el pendiente del 2026-08-01) y variante card APROBADA. Quedan row e inline. docs:check 0 sobre 564 documentos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
5ff5202038 |
docs(sound-engine): el stamp de sema y la matriz del modo audio, medidos
Cierra los dos pendientes del hilo que no exigían los ojos del usuario, y sanea el documento, que afirmaba cosas ya cumplidas. STAMP DE SEMA — cerrado por las dos mitades. Dónde aterriza: con un MutationObserver sobre el subárbol del player, un clic en play estampa contact-activate y commit-toggle-play (commit · neutral) sobre el PROPIO play-button, no sobre el provider, y los limpia al cerrarse el hold. Que la regla del pack casa: A/B del pico de la envolvente dentro del mismo componente — transporte (play, mute) 0.0001, o sea silenciado; commit-complete 0.05, audible. Los osciladores sí se construyen, la ganancia nunca sube: es la resta de gain de D-AP2.7 funcionando. Queda anotada la trampa en la que caí, comparar ese pico con el 0.25 de un Button de otra página y creer ver una contradicción: el A/B tiene que ser dentro del mismo componente. F6, MATRIZ DEL MODO AUDIO — las 16 combinaciones (4 variantes × claro/oscuro × LTR/RTL) recorridas buscando partes colapsadas, botones estrujados y desbordes. Un solo defecto: en `bar` el time-slider colapsa a ancho 0 y el mute a 20px, en las CUATRO combinaciones, así que no es del eje ni del tema. Visto: el scrubber queda reducido a un punto ámbar entre 0:00 y 6:12 — una barra de app sin barra de progreso — mientras el volumen conserva su ancho completo. `card` y `row` limpias; lo que oculta `inline` es lo que su receta declara. HIGIENE — el documento decía «sin commitear» del rediseño y del waveform, y pedía un re-plan del player que ya está hecho. Verificado contra git: rediseño |
2 months ago |
|
|
1cb61567e5 |
feat(direction): RTL-2 — el guard del doble volteo, la forma que se me colo dos veces
Cierra §10.1, la ultima deuda del eje. `dee2c9e3a` arreglo los dos defectos pero
dejo la FORMA sin guard, y es la que sobrevivio a la migracion §6.3 y a que yo
diera el eje por cerrado en §9.19.
LA FIRMA — dentro de un bloque cuyo prelude tiene `:dir()`, la misma familia
logica (`border`/`padding`/`margin`/`inset`-`inline`) declarada en LAS DOS caras,
EXACTAMENTE UNA con valor neutro (`0`, `auto`, `none`, `initial`, `unset`,
`revert`). Ese desequilibrio ES el espejo cancelado: la propiedad ya se habia
volteado cuando la regla matchea, asi que reubicarla la devuelve al punto de
partida y la deja en el borde opuesto al de sus hermanas.
Es mas estrecha que «bloque `:dir()` con puras logicas» a proposito. Una regla
que CAMBIA un valor bajo RTL sin reubicarlo es legitima —asimetrico por diseño
existe—, y las dos caras con valor son un autor describiendo dos bordes reales.
Lo que delata el defecto es el PAR, una cara apagada. Hay un test negativo por
cada uno de esos casos, que son los que evitan que la regla se vuelva ruido.
LAS TRES DECISIONES que el handoff dejaba abiertas:
- `:dir(ltr)` tambien entra. Igual de sospechosa, y no anade ruido.
- Marcador PROPIO, `rtl-mirror: <reason>`. Reutilizar `rtl-physical:` mentiria:
aqui no hay nada fisico, y un marcador que miente es peor que ninguno. Mismo
mecanismo (`exemptLines` toma ahora el patron por parametro), dos vocabularios.
Un test comprueba que el marcador de RTL-1 NO exime a RTL-2.
- Regla aparte, `lintRtlMirror()` con su propio `RtlMirrorFinding`. Los 14 tests
de RTL-1 quedan intactos y el tipo lleva los campos que importan
(`family`/`neutral`/`payload`) en vez de forzar los de RTL-1.
VERIFICACION, en este orden:
- `rtl-lint.test.ts` 27/27 (14 de RTL-1 intactos + 13 nuevos).
- RECALL con el runner COMPLETO contra el arbol pre-arreglo: restaure los dos
ficheros de `dee2c9e3a^` sobre el arbol, corri `rtl:check` y los devolvi con
`git checkout` en el mismo bloque. **2/2 cazados** (`feed.css:147`,
`tree-view.css:211`). Probar la funcion no basta: el runner es lo que corre.
- `rtl:check` sobre HEAD: 1 error, el de `palabras`, preexistente y excluido.
- `docs:check` 0/0 sobre 564 docs. `check` 77 = linea base.
Un defecto de presentacion salio al hacerlo: el `calc()` multilinea de tree-view
partia el mensaje por el primer salto. Los campos que RTL-2 emite colapsan el
whitespace; con test.
Actualizados los textos que el handoff avisaba que quedarian obsoletos: el
`enforcement:` y §4 del contrato, la fila RTL del build contract, la fila X-1.6
del checklist, y los docblocks de `rtl-lint.ts` y `rtl-check.ts` — que son
doctrina, no adorno.
⚠️ Sigue siendo cierto lo que NINGUNA de las dos reglas ve: leen texto CSS. Un
`transform` inline escrito por JS, un preset de motion compartido y la geometria
SVG siguen necesitando el ojo en RTL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
7aee33acda |
docs(direction): §10, la cola para manana — RTL-2 ya viene especificada y medida
La unica deuda del eje es el guard del doble volteo, y es la forma que se me
escapo DOS veces (en la migracion §6.3 y en el cierre §9.19). El handoff no la
deja como idea: la deja lista para implementar.
LA FIRMA, mas estrecha que «bloque `:dir()` con puras logicas» —eso daria falsos
positivos sobre reglas legitimas que cambian un valor bajo RTL—: dentro de un
bloque con `:dir()`, la MISMA familia logica aparece como `-start` y como `-end`,
una con valor NEUTRO (`0`/`auto`/`none`) y la otra cargando el valor. Ese es el
gesto «apago un lado y lo repinto en el otro», que cancela un espejo en vez de
crearlo.
MEDIDA ANTES DE ESCRIBIRLA, que es lo que la hace accionable:
- contra `dee2c9e3a^` (antes del arreglo): 2/2 cazados
- contra HEAD: 0 hallazgos sobre 18 bloques `:dir()`
Nace verde y con recall demostrado sobre defectos REALES. Si al implementarla
sale distinto de 0, es un hallazgo nuevo, no un fallo de la regla.
Con las cuatro piezas a tocar (`rtl-lint.ts` y su docblock —que es doctrina—,
`scripts/rtl-check.ts`, y los casos de test, positivos Y negativos), tres
decisiones que NO dejo tomadas (`:dir(ltr)` tambien; escape hatch propio o
reutilizar `rtl-physical:`; regla aparte o ensanchar RTL-1), y el aviso de que
al aterrizar quedan DOS textos obsoletos el mismo dia — los escribi diciendo que
el guard no ve esta forma.
⚠️ NO copiar mi prototipo: usa una regex ingenua sin anidamiento ni comentarios.
`rtl-lint.ts` ya tiene `parseBlocks()`, que da exactamente lo que hace falta.
§10.2 lleva el resto por valor (los charts deberian aceptar `dir` — la
divergencia declarada en §7 del contrato; la tabla de props ausente de
number-field; la numeracion duplicada preexistente del checklist) y §10.3 el
estado del arbol.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
961c1bf8be |
docs(direction): los 55 componentes que aceptan `dir` ya lo documentan
De los que aceptan la prop, la mayoria no la mencionaba en su README, y los que
si tenian seis redacciones distintas para el mismo hecho: «from Soma», «prefs»,
«soma config», «soma», «Override Soma defaults» y —la peor— «Inherited from Soma
when omitted», que se lee como herencia del PADRE, que es justo lo que la cadena
no hace.
Cada uno dice ahora QUE cambia la direccion EN ESE componente, leido de su propio
codigo (el JSDoc de la prop, los usos de `resolvedDir` en el provider, las reglas
`:dir(` de su receta), no una frase de plantilla. Y cada uno enlaza el contrato
una vez, en la primera mencion.
DOS AFIRMACIONES FALSAS, cazadas por el verificador adversarial:
- `feed` decia que la receta «mueve el rail del hilo al otro lado». No lo movia:
lo CANCELABA. Ese era el defecto arreglado en `dee2c9e3a`; el README describe
ahora lo que el codigo hace.
- `waveform` daba `prefs` como default. Falso: waveform es el caso DELEGANTE,
no llama a `activeDir`, y sin prop no hay atributo ni lectura de prefs —
hereda. Su Slider compuesto es quien corre la cadena.
Y dos que estaban mal COLOCADAS: `calendar` listaba `dir` bajo `## ARIA` (no es
un atributo ARIA) y `accordion` bajo `## HTML Attributes` describiendolo como
«la prop cruda», cuando lo estampado es prop O prefs.
CORRECCION AL PRIMER BARRIDO, por la ley de autoria: escribio la cadena literal
`prop → prefs → 'ltr'` en 54 ficheros. Eso es la forma restate-instead-of-link
que `docs/authoring.md` §1 prohibe citando este mismo fallo («'7 families'
sobrevivio en tres docs semanas despues de que el canon pasara a 8»). Retirada:
la cadena se enuncia UNA vez, en el contrato; el README conserva su efecto.
Tres READMEs de eidos ensenaban todavia el selector prohibido —y los tres se
equivocaban sobre su PROPIO CSS, que ya usa `:dir(rtl)`—: waveform, nav-tree y
toolbar (este ademas invertia la precedencia: «prefs o prop»; gana la prop).
⚠️ `number-field` no tiene tabla de props que ampliar; su `dir` sigue en su
seccion `### Direction resolution`, ahora enlazando el contrato. Fabricar una
tabla de una fila diria «`dir` es su unica prop».
Handoff §9.20 con el cierre, el doble volteo y como se colo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
f9e372e6fe |
feat(media-player): captions propias + el eje de dirección, canonizado
Dos hilos que comparten ficheros, así que van juntos. ## Captions — el player pinta las cues La parte estaba declarada en el morfo desde el principio y nunca implementada. El render nativo no servía: con la pista en mode='showing' el navegador dibuja su caja de cues en bottom:0 del <video> — la misma franja de 49px que la barra de controles — y contra el z-index:4 de la barra al 82% de opacidad llegaba el 18% del texto. ::cue no puede moverlo (la spec no admite propiedades de posición ahí). Ahora la pista se mantiene en 'hidden': la spec deja la pista activa —cues parseadas, activeCues mantenida, cuechange disparándose— y el navegador no pinta nada, así que el texto se monta en la parte Captions, por encima de la barra. Es el movimiento de Plyr, y por el mismo motivo. Tres trampas de la especificación, encaradas por escrito: - Pasar una pista a 'disabled' desactiva sus cues SIN disparar cuechange. Nadie avisa de que hay que borrar, así que se limpia a mano — si no, el último subtítulo se queda congelado sobre el vídeo. - Los eventos de cue no corren mientras está puesto el show-poster flag, así que el primer pintado se siembra leyendo activeCues. - cues/activeCues son null en 'disabled'. De paso cierra un defecto latente: captionsOn era un espejo de una sola dirección que nunca releía track.mode, así que si el SO activaba subtítulos salían cues mientras el botón decía que estaban apagados. Ahora se DERIVA de los modos reales, y una pista que el navegador encienda se adopta (degradada a 'hidden', para que no pinte encima de la nuestra). Decisiones declaradas: el marcado inline se conserva (se monta el DocumentFragment de getCueAsHTML como nodos, nunca por innerHTML — el sumidero de XSS que abre Plyr al serializarlo de vuelta a cadena); la metadata de posición (line/position/align/vertical/snapToLines/regiones) se DESCARTA. Esto no es una implementación de WebVTT y el README lo dice. ## Dirección — el player estaba partido por la mitad Con dir="rtl" por prop y sin dir ambiental, el cromo se disponía en LTR y sus dos Sliders en RTL: el wrapper pasaba la prop cruda y sólo la reenviaba a los hijos, sin resolver la cadena ni estampar. En la demo estaba enmascarado porque el stage estampa dir además de pasar la prop. Canonizado con el patrón de CONTINUE-direction.md §9.16: activeDir en el wrapper, resolvedDir en el provider, dir crudo + data-dir resuelto en los props. El censo del §9.15 no lo tenía — era un cuarto caso que su búsqueda no cubre (acepta dir y no lo escribe en ningún sitio). Y los glifos direccionales no se volteaban: en RTL el transporte se leía ▶▶ ▶ ◀◀, con las dos flechas exteriores apuntando HACIA DENTRO — el síntoma que carousel.css ya documentaba. Se voltean con scale:-1 1 (no rotate:180deg: sólo coinciden en glifos verticalmente simétricos, y rotar el PiP le llevaría el recuadro a la esquina de arriba), anclado a data-dir, el atributo propio del componente. Alcance elegido: seek, play, volumen y PiP; captions y fullscreen NO. El teclado pasa a resolver por getDirectionalKeys, así que las flechas concuerdan con el glifo y con el Slider embebido. ## Verificación check 0 errores propios · vitest del player 14/14, con los tres guards nuevos vistos en ROJO por mutación · eidos-lint invalid 0 (morfo-backed 25 → 29) · morfo:check sin fallos del player · component:audit PASS · smoke PASS · docs:check 0 · prettier limpio en lo propio. A ojo en Chrome real: claro y oscuro, LTR y RTL, y las dos regresiones (el espejo del SO y el subtítulo congelado). Nota: el README de soma se lleva una línea ajena — la fila `dir` de la tabla de props, del barrido de otra sesión, que describe justamente esta cadena. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
76c807d3c7 |
docs(direction): 9.19, el alias y los cuatro de solo-CSS — el eje queda cerrado
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
bfb58b9c78 |
docs(direction): 9.18, la auditoria de la canonizacion
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
3c73af90a3 |
docs(direction): 9.16 grupo A canonizado, 9.17 el grupo B es decision de API
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
8cda5fd252 |
docs(direction): 9.15, censo de canonizacion — 38 canonicos, 11 fuera de la norma
El censo de 6.12 estaba incompleto: contaba los wrappers con default duro pero no los que resuelven en el provider (4) ni los que no exponen dir (7). Ninguno esta roto hoy — verificado que number-field y scroll-area estampan lo mismo — pero quedan dos decisiones: mover la cadena al wrapper, y si los 7 sin prop deben exponerla. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
7b306ae5d7 |
docs(direction): 9.13 decidida (teclado fisico) y 9.14, el resize del float-panel en RTL
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
013af67d7b |
docs(direction): 9.12 color-picker cerrado por las referencias, 9.13 float-panel sin precedente
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
ac2ca97444 |
docs(direction): 9.11 ejecutada — el lang del documento ya sigue al idioma
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
23d2e94783 |
docs(direction): el dir vacio era el shorthand {dir} de Svelte, y el diagnostico del html lang
9.10 cierra el dir=\\` que 6.5 dejo sin investigar: el shorthand emite una asignacion de PROPIEDAD ademas del atributo, y con undefined queda la cadena vacia. No estaba en el SSR — lo escribia el cliente al hidratar. 9.11 deja el html lang diagnosticado pero NO ejecutado: la causa es app.html con lang=en hardcodeado, el arreglo seria corto (la shell ya registra language en prefs) pero cambia el contrato ownsAttrs de la proyeccion, y hay un argumento en contra — en apps con i18n por routing el lang lo fija el servidor. Decision de arquitectura, pendiente de criterio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
3bfe2c6527 |
docs(direction): 8.3 cerrada — coarse eran 6 media queries, no 2, y el swipe con raton real
Seccion 9.9. Dos cierres y un hallazgo del usuario: - `pointer: coarse`: el catalogo tiene SEIS media queries, no las dos que 8.3 daba por unicas. Las cuatro sin mirar (list-surface, chat-message, radio-group, rotate-align) no tienen ninguna geometria de eje inline — `--list-item-height`, `opacity`, `min-block-size` e `inset: 0` + `min-*` logicos. Las dos que si la tenian son justo las que 6.2 midio con hit-test. Cerrado por lectura, que aqui basta porque lo que se comprueba es una AUSENCIA. - El swipe del carousel con el raton REAL del navegador: las 4 combinaciones dan espejo exacto. Cierra lo que 6.8 no consiguio disparar por simulacion. MÉTODO: el raton real avanza 2 posiciones de golpe (momentum) y `left_click_drag` no deja fijar la velocidad — lo que se comprueba es el SIGNO, no la magnitud. Y verifica la direccion ANTES de cada gesto: mi primera pasada midio creyendo estar en LTR con el toggle en RTL, y el resultado parecia un defecto inexistente. - Las flechas del carousel en RTL (arreglo en el commit anterior). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
07515eedf5 |
fix(motion,demos): dos desviaciones propias, encontradas auditando la sesion
1. La regla `:dir()` de `3c3d8e1ac` nacia SIN ACOTAR, o sea matcheaba cada elemento del documento para declarar una custom property que solo lee el nodo animado. Acotada a `[data-animation-style]:dir(...)`: `:dir()` se resuelve contra ese nodo, asi que el caso del subarbol anidado sigue cubierto y el coste deja de ser global. Medido igual que antes — origin 65.05px en RTL / 0px en LTR, con el borde de anclaje clavado en 0. 2. `releasePosition()` en la demo del float-panel (`1857c7854`) soltaba la posicion SIEMPRE, tambien con `useAnchor` apagado: cambiar `side` en modo libre re-centraba un panel colocado a mano. Condicionado al ancla, que es lo unico con lo que la posicion fijada es excluyente. Medido: con ancla off el panel se queda en (24,24); con ancla on sigue re-sembrando (start x=234 · end x=61). Y el hueco de metodo que las destapo: habia tocado `render-css.ts` —el generador de CSS de TODO eidos— corriendo solo los tests del ambito. La pasada COMPLETA da 8 fallos en 4 ficheros, y verifique que ninguno es mio cruzando lo que señalan contra `git diff --name-only 40db0b981..HEAD`: audio-player (sonido), aura + menubar (eje agentico), orca (hook timeout), soma-attr-audit (pasa en solitario: flaky bajo carga). Queda anotado en 9.7 un cabo suelto AJENO: 6.7 retiro la entrada de `data-ready` de `component-visual-attrs.test.ts` al migrar tabs, pero `contracts.test.ts` sigue marcando `data-ready` en radio-group y tabs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
8ec1095545 |
docs(direction): las tres piezas de graficas, y el tercer punto ciego de RTL-1
Seccion 9.6: cierre de metrics / bar-segment / chart-legend, el defecto del preset `grow-x` con sus medidas, y el renombrado de `placement`. Lo que merece quedar escrito: los tres se dieron por sanos en 7.2 mirando el CSS, y el CSS ERA correcto en los tres. Lo que fallaba era la ANIMACION, que no vive en el recipe sino en un preset compartido. Revisar una grafica en RTL no es solo leer su recipe — hay que disparar su entrada y mirar de donde crece. Y el tercer punto ciego de RTL-1, tras el transform inline del 6.7: un `transform-origin` en un preset de TypeScript. El guard lee CSS estatico; los presets y lo que pinta el JS siguen necesitando el ojo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
3812cd196e |
docs(direction): cierre de 8.1 y 8.2, y la leccion de por que una demo no deja ver
Seccion 9 nueva: el eje horizontal del scroll-area (9.1-9.4), los tres
agujeros de observabilidad (9.5) y los sueltos (9.6). 8.1 y 8.2 marcadas
CERRADAS con puntero.
La leccion que se repitio tres veces y merece quedar escrita: una demo es
inobservable por motivos DISTINTOS, y hay que averiguar cual antes de
declarar el bloqueo —
1. el control existe y basta usarlo — "scroll-area no tiene eje horizontal
(maxScroll: 0)" era cierto solo en el valor por defecto; el chip
scrollbars="both" daba 258. El bloqueo no existia y no hubo que
construir nada.
2. falta el control — el drawer solo listaba los cuatro fisicos.
3. el control existe pero otro estado lo anula — float-panel tenia
useAnchor, pero una posicion ligada ganaba siempre.
Recorre los controles y mira QUE GANA.
Queda anotado, señalado y no tocado: 2.4 sigue viva (parts[2] de la demo del
slider indexa SecondaryRange en vez de Thumb, ahora linea 430) y float-panel
no expone prop dir — lee soma.prefs.getDir() directo, que es la cadena
documentada con el eslabon de prop ausente.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
40db0b9816 |
docs(direction): cierre del barrido de graficas, la regla de RTL en SVG, y la cola para manana
LAS 11 GRAFICAS DEL CATALOGO, REVISADAS. Ninguna de las siete que quedaban
necesitaba cambios, y por razones distintas que conviene dejar escritas:
bar-list ya lo resuelve con CSS logico; sparkline y bubble-chart heredan el
frame de <Chart> arreglado en 55070c688; y gauge, pie-chart, polar-area y
smith-chart son RADIALES — sus posiciones son angulos, no tienen eje de lectura
y es correcto que no espejen.
LA REGLA, ahora en la fila "RTL · SVG" del contrato de construccion y
desarrollada en eidos/components/chart/README.md §Direction. Son DOS decisiones
y no son la misma:
1) ¿espeja la composicion? solo si hay EJE DE LECTURA. Se invierte el rango de
pixeles de la escala, o se refleja la x. Los valores de Y no se espejan
nunca — un grafico es una secuencia leida como lee el lector, no un espejo.
2) `text-anchor` es LOGICO: si la composicion espeja se deja tal cual (voltear
ademas lo cancela); si no espeja se fuerza el fisico.
TRAMPAS DE MEDICION que me engañaron durante toda la sesion, en §7.3: el bbox de
un path espejado es identico al del original —mi "fingerprint" declaro sparkline
sin espejar cuando si lo hacia—; un test de "se sale del SVG" no ve una invasion
hacia DENTRO; el hueco del eje Y hay que medirlo contra el TICK y no contra la
linea; verificar en LTR no revela NADA de un defecto de RTL; y la pagina
"heatmap" tiene dos modos tras un control, asi que capturar el primer svg solo
enseñaba uno.
HANDOFF. La cabecera apunta a la §8 nueva, que es la cola priorizada: scroll-area
(dos señales, sin medir, y le falta una demo con eje horizontal), los agujeros de
OBSERVABILIDAD que impiden verificar drawer y float-panel, las verificaciones
pendientes, y la deuda ajena por gravedad —encabezada por los 91 ficheros de test
que falsifican Soma.require()—. Y una §8.5 con lo que NO hay que rehacer, para
que nadie repita el trabajo medido.
check 74 = linea base exacta.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
714a1ddb59 |
fix(carousel): en RTL el rail se movia AL CONTRARIO del dedo durante el arrastre
El usuario vio a mano lo que mi simulacion de puntero no consiguio disparar en cinco intentos. El swipe estaba mal, y no donde yo habia mirado. La decision de finishDrag SI voltea (isRtlHorizontal ? -offset : offset) y por eso la daba por buena. El defecto estaba en el PINTADO durante el arrastre: translate = base + dragOffset // base = indice, dragOffset = dedo itemGroupTransform = translate * flip // el flip caia sobre LOS DOS El recorrido por indice es LOGICO —N->N+1 mueve el rail a la izquierda en LTR y a la derecha en RTL— y debe voltear. `dragOffset` es el desplazamiento FISICO del puntero y no debe. Al voltear ambos, en RTL el rail se movia en sentido contrario al dedo: arrastras a la derecha y el contenido huye a la izquierda. Separados: solo baseTranslate lleva el signo. A/B con git stash, midiendo el transform A MITAD del arrastre —que no exige superar el umbral de gesto, y por eso ahora si es medible—: sin el arreglo, con el dedo a +100px el rail da Δ-100 (huye); con el arreglo, Δ+100 (lo sigue). LTR intacto en los dos casos. NOTA DE METODO, anotada en el handoff: para un gesto con umbral de distancia y velocidad, no intentes completar el swipe con page.mouse; mide el estado INTERMEDIO con el puntero aun abajo. Es lo que me faltaba en §6.8. DESCARTADOS CON MEDIDA, no se tocan: scroll-area FUNCIONA. Confirmado por el usuario y medido: con el toggle en auto, ES->ltr y AR->rtl, y el elemento pasa a direction:rtl. Su resolvedDir sigue siendo codigo muerto y el uso de scrollLeft sin verificar en un eje horizontal real, pero el componente responde a la direccion. chart NO NECESITA ARREGLO y las referencias lo respaldan. Medido: chartDir, legendDir y el texto de los ejes pasan de ltr a rtl; el eje numerico no se voltea. Es el consenso: Chart.js implemento RTL SOLO para leyendas y tooltips y mantiene abierto el issue de ejes/etiquetas (#11937, PR #6460), y el patron documentado es eje numerico LTR con textos RTL. amCharts es de los pocos con RTL integral. Y el sintoma «la demo no reacciona al idioma», reportado tres veces (virtual-list, carousel, scroll-area): medido en las tres, con el toggle en AUTO las tres reaccionan. Si el toggle esta en ltr/rtl explicito el idioma no manda, porque el intent gana sobre la derivacion. No es defecto, pero despista de forma sistematica: parece un interruptor de dos posiciones y es un ciclo de tres donde solo uno cede el mando al idioma, y el estado solo se lee en el title. Cerrarlo seria trabajo de la shell, no del eje. Verificado: check 74 = linea base exacta. 4/4 en carousel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
f420c74920 |
fix(rating-group,splitter): dos wrappers se quedaron fuera de 013ceac57, y el arrastre del splitter no volteaba
Verificados los tres componentes que "tenian codigo de direccion" pero nadie habia comprobado que acertara. Dos estaban rotos. RATING-GROUP, roto por DOS motivos encadenados. 1) calcFromPointer medía (clientX - rect.left) / rect.width, una fraccion desde el borde FISICO izquierdo. En RTL los items van de derecha a izquierda, asi que la mitad que EMPIEZA una estrella es la derecha: pinchar la primera mitad visual daba la estrella entera. Volteado con el mismo `1 - pos` que ya usa el slider. 2) Y aun asi seguia sin funcionar, porque su WRAPPER se quedo fuera de la normalizacion de 013ceac57: tenia `dir = 'ltr'` como default duro —que ademas incumple "el atributo nunca tiene defecto"— y readableActive(() => dir) en vez de activeDir(() => dir, soma). Nunca consultaba prefs, asi que resolvedDir valia 'ltr' pasara lo que pasara. NATURAL-TIME-PICKER tenia el mismo agujero (dir ?? 'ltr'). Son los DOS unicos que quedaron fuera; los otros 36 si usan activeDir. Medido tras arreglar ambos: en RTL la mitad derecha da 2.5 y la izquierda la entera — espejo correcto. SPLITTER: el arrastre estaba roto, el teclado no. resizePanels toma un delta LOGICO —positivo agranda el panel de ANTES del handle— y recibia el delta fisico del puntero sin voltear. En RTL arrastrar a la derecha agrandaba el panel que debia encoger, y el arrastre CONTRADECIA a las flechas, que si pasan por getDirectionalKeys. Tras el arreglo: LTR arrastre +118 / ArrowRight +6; RTL arrastre -118 / ArrowRight -6 — espejo exacto y los dos de acuerdo. NOTA DE METODO, anotada en el handoff: al ver `resolvedDir = opts.dir.current ?? 'ltr'` en unos 30 providers di por hecho que a todos les faltaba el eslabon de prefs. Es FALSO — la cadena vive en el WRAPPER (activeDir), y el provider recibe la prop ya resuelta. El slider, que tiene ese mismo resolvedDir, voltea perfectamente. Antes de "arreglar" 30 componentes, mirar el wrapper. SIN VERIFICAR: scroll-area. Su resolvedDir aparece UNA sola vez —la declaracion—, o sea es codigo muerto, y usa scrollLeft en cuatro sitios sin tratar que en RTL los navegadores lo devuelven NEGATIVO (isAtLeft seria siempre cierto, el ratio del thumb saldria negativo). No esta medido porque el scroll-area de la demo no tiene eje horizontal. Verificado: check 74 = linea base exacta. 20/20 en splitter + rating-group + natural-time-picker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
b550c8cd4b |
fix(virtual-grid,virtual-list): en RTL el contenido desaparecia — el ancla de celda era fisica sobre un sizer logico
Reportado por el usuario: en la demo del virtual-grid, voltear la direccion deja
la rejilla EN BLANCO. Reproducido y medido en Chrome.
LTR : 108 celdas, 70 visibles, primera en x=381 (borde del viewport)
RTL : 108 celdas, 0 visibles, primera en x=-5101 (fuera de la pantalla)
No era scrollLeft —esta en 0 en ambos casos—, era el anclaje de la celda:
position:absolute con top:0, left:0 y translate3d(columnStart). Los dos terminos
son fisicos y coherentes ENTRE SI, pero el sizer interno es
`inline-size: 6000px`, o sea LOGICO: en RTL desborda hacia la izquierda y ocupa
[-5101, 899], asi que su borde fisico izquierdo es donde el contenido TERMINA.
`left: 0` mandaba las 108 celdas justo ahi.
Arreglado con ancla logica (inset-inline-start) y el signo en el offset, de modo
que el recorrido se queda en translate3d: un virtualizador recoloca miles de
celdas y conviene que siga en el compositor en vez de animar layout. Medido tras
el arreglo: 70 visibles en RTL, primera en x=779 = borde derecho del viewport
menos el ancho de celda, que es el inicio logico.
virtual-list tenia el MISMO patron (left:0 + translateX) y por tanto el mismo
defecto en modo horizontal. Mismo arreglo. El modo vertical no se ve afectado:
inset-inline-start:0 con width:100% es exactamente lo que left:0 era. Verificado:
12 items visibles en RTL, primero en x=755.
EL OTRO SINTOMA REPORTADO NO ES UN DEFECTO. «El cambio de idioma no le afecta»:
con el toggle en `auto`, elegir arabe voltea la lista correctamente —medido,
htmlDir y el direction computado del componente pasan a rtl—. Si el toggle esta
en ltr/rtl EXPLICITO el idioma no manda, porque el intent gana sobre la
derivacion; es lo ratificado en
|
2 months ago |
|
|
caf3cfd884 |
fix(float-panel): la semilla anclada usa la matematica de $ethereal en vez de una copia sin direccion
Verificados uno a uno los cinco componentes de la §2.2 del handoff, y despues barrido el punto ciego del guard. Ningun cambio de comportamiento salvo el de float-panel. LOS CINCO DE §2.2 ESTAN SANOS. slider: click al 25% del ancho FISICO da 75 en RTL, arrastrar a la derecha sube en LTR y baja en RTL, y ArrowRight igual. number-field y css-field: el mismo arrastre fisico sube en LTR y baja en RTL. dropdown-menu: el panel raiz se alinea al inline-start y el submenu abre a la derecha en LTR y a la izquierda en RTL. carousel: el track voltea (Next mueve -622px en LTR y +622px en RTL). Ninguno necesito arreglo. EL SWIPE DEL CAROUSEL NO ESTA MEDIDO. Cinco intentos de simulacion de puntero y no consegui disparar el gesto de forma reproducible ni en LTR: la capa tiene umbral de distancia y de velocidad. El signo esta bien por LECTURA (isRtlHorizontal ? -offset : offset). Son diez segundos con un raton de verdad. UN MOTOR CUBRE NUEVE COMPONENTES. soma/layers/floating lo consumen combobox, context-menu, dropdown-menu, link-preview, menubar, popover, select, sidebar y tooltip. Verificado a traves de popover (align=end: derecha en LTR, izquierda en RTL) y menubar (align=start, al reves). Los nueve quedan cubiertos. FLOAT-PANEL NO USA ESE MOTOR, Y HACE BIEN: useFloating mantiene el panel PEGADO a su ancla, y un float-panel se arrastra y redimensiona libremente. Pero su SEMILLA anclada si era redundante: computeInitialPosition() re-derivaba side + align contra el rect a mano, y esa copia resolvia align:'start' a r.left SIEMPRE — una alineacion LOGICA clavada a un borde FISICO, que nunca voltea. $ethereal ya exporta computeCoordsFromPlacement(rects, placement, rtl): pura, sincrona, sin ciclo de vida, y es la misma que usa el motor compartido. Migrada la semilla a ella. Se comparte la semilla, NO useFloating. anchored-seed.test.ts fija la migracion, porque la demo NO ancla ningun panel y el defecto no era observable ahi: en LTR el resultado es IDENTICO al algoritmo anterior en las 12 combinaciones side x align (cero regresion); en RTL con side vertical start y end se espejan; con side horizontal el align no cambia, que es el eje de bloque; y center sigue centrado en ambas. La demo, medida antes y despues, no se mueve. NO TOCADO, hace falta criterio: Home/End en el eje X llevan el panel a bounds.left / bounds.right. Para un panel que se arrastra libremente es defendible que sean extremos fisicos y no principio/final de lectura. Descartados como falsos positivos tras mirar el uso real: container (prop de margenes), text-focus, text-scramble y path-trace (miden donde esta algo para un efecto), drag-drop (sueltas donde ves), cropper (paneas una imagen que no se voltea) y gradient-builder (arrastras la parada donde la ves). Y drawer, que no aparecia en el primer cruce, esta BIEN y es el ejemplo a imitar: traduce start/end a left/right una sola vez en resolveDirection() y desde ahi todo es fisico y coherente — aunque su demo solo expone valores fisicos, asi que su mitad logica no es observable. Verificado: check 74 = linea base exacta. 12/12 en float-panel + 4/4 del test nuevo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
76f18a5e87 |
refactor(tabs): el indicador pasa a MeasuredIndicator, y aparece un punto ciego del guard
Buscados los indicadores que comparten mecanismo con el de |
2 months ago |
|
|
dc5e956658 |
fix(direction): el indicador deslizante no se remedia al voltear la direccion
Verificar los tres arreglos que quedaron sin ver en
|
2 months ago |
|
|
ec533845d1 |
feat(direction): un guard para el eje logico-fisico, y el <main> que congelaba las 51 demos
Cierra la §2.1 del handoff. El guard nacio en rojo con 24 hallazgos y al arreglarlos aparecieron dos defectos mas grandes que el que buscaba. 1) EL GUARD RTL-1. Nada cruzaba un ancla LOGICA con un desplazamiento FISICO en la misma regla CSS: eidos-lint clasifica SELECTORES, no declaraciones, y la fila RTL del contrato de construccion decia "rule (LIVE)" — revision humana. Por eso el defecto del slider llego a produccion y lo cazo el ojo del usuario. Ahora `npm run rtl:check`, con la logica en src/uix/eidos/rtl-lint.ts (14 tests) para que sea testeable, calcando el par lint.ts / scripts/eidos-lint.ts. El test ancla el caso historico: slider.css ANTES de |
2 months ago |
|
|
86b6381388 |
docs(process): handoff del eje de direccion
Que esta cerrado (3 commits), que queda por orden de valor, y las trampas de metodo que costaron horas. Lo primero de la cola NO es codigo de componente: es el guard que no existe. Nada cruza `transform: translate*` con `inset-inline*` — hay 39 transforms en el CSS de eidos y ningun script los mira, porque eidos-lint solo clasifica selectores y el guard de la fila RTL del contrato es literalmente revision humana. Por eso el bug llego a produccion y lo cazo el ojo del usuario. Incluye la investigacion de las cinco librerias de referencia ya hecha (25 agentes, 5 hallazgos tumbados por refutacion) para que nadie la repita, y los dos datos que mas costaron: `unicode-bidi: isolate` NO arregla el interrogante desplazado, y `dir="ltr"` si — porque la hoja de agente del WHATWG le da isolate de propina. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
538f4932bc |
docs(process): el handoff decia que el Slider RTL estaba roto, y ya no lo esta
Cerrado en |
2 months ago |
|
|
42d58c3940 |
docs(process): handoff del RTL — el Slider esta roto y es la prioridad de manana
Nace CONTINUE-player-rtl.md con lo que la MIRADA del usuario abrio el 2026-08-01 y quedo sin cerrar, mas el enlace cruzado desde el handoff del hilo de audio. Lo que lleva: 1. PRIORIDAD — el Slider esta roto en RTL, confirmado por el usuario con el componente SUELTO. No es del media-player: es el canonico del que cuelgan waveform, media-player, color-picker y los dos time-pickers. Se deja descartado por lectura que getValueFromPointer y rangeStyle estan bien en aislamiento, y se deja una HIPOTESIS comprobable en dos minutos: el Slider estampa `dir` como atributo HTML, lo que activa `direction: rtl` en CSS, y slider.css mezcla propiedades logicas con los estilos fisicos en linea que calcula el provider — doble volteo. El arreglo sera elegir UN dueno de la inversion. 2. La disposicion de los botones del player, que el usuario insiste en que es un problema APARTE. Las sondas de la sesion decian que mirroriza, pero esas sondas fallaron varias veces ese dia (dos midieron video creyendo audio): quedan marcadas como NO fiables. 3. Iconos de seek: decision tomada (voltearlos, con su razon), sin aplicar. 4. La medicion del escenario mixto quedo a medias y se explica por que: se instrumento DESPUES del arranque, asi que el "0 contextos" no prueba lo que parecia. Lo que si queda establecido es que la media no abre ningun AudioContext. 5. Captions y el cableado del waveform, con su diagnostico completo. Y las lecciones de metodo del dia, que son el motivo de que este documento exista: medir el contrato NO es verificar la experiencia (se afirmo tres veces lo contrario y las tres las caza el usuario); el panel embebido no sirve para nada visual (innerWidth 0 — llego a inventar tres "overflow" inexistentes); comprobar que el control se pulso de verdad antes de creerse la medida; la distancia RGB es ciega al croma; y la FORMA manda sobre el color. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
58acb778ee |
docs(waveform): F6 firmada — la iniciativa queda completa
El usuario dio por bueno el waveform contrastandolo con el reproductor de Suno AI, que valida dos decisiones del gate: barras (D-WF.5, la lectura SoundCloud) y los dos colores reproducido/pendiente como lo esencial — Suno ni siquiera pinta buffered, que en el nuestro es opcional y arranca apagado. Queda anotado lo que costo la mirada, porque ninguna sonda lo habia visto: el anillo del commit enmarcando el control entero ( |
2 months ago |
|
|
e096f65e39 |
docs(sound): MediaSession verificada por el usuario — el pendiente de tres sesiones, cerrado
El usuario abrio el media hub de Chrome con la demo de audio sonando: caratula + titulo + artista + transporte, origen localhost:5173. Cierra "prueba manual de teclas de medios / pantalla de bloqueo", pendiente desde que se escribio el rediseno porque no habia pagina que consumiera sound.media(). Se anotan tres cosas para no repetir el camino: - Las teclas de VOLUMEN no sirven (muestran el OSD de Windows; en Win11 Microsoft retiro el panel de medios que se pegaba a ese OSD). La superficie fiable es el boton de nota musical de la barra de Chrome. - La sospecha sobre la caratula era INFUNDADA: se temio que artwork sin sizes/type y sin CORS fuera descartado por el MediaImageManager. Carga perfectamente. Queda registrado para que nadie "arregle" un no-problema. - Anterior/siguiente apagados es CORRECTO: setActive() registra play, pause y seekto; un reproductor de una sola fuente no tiene lista. Y la leccion de metodo: se afirmo "el audio ya funcionaba" apoyandose en una medicion JS (metadata poblada) como si zanjara lo que el usuario VE. Una medicion del contrato no es una verificacion de la experiencia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
df86a4e509 |
fix(slider,media-player): el anillo del commit no enmarca contenedores, y saltar al final no es completar
Cuatro defectos que solo aparecieron cuando el usuario MIRO la pagina.
1) EL BORDE DE LOS SLIDERS ("se viene arrastrando desde el principio").
No era del arquetipo ni del slider: es la firma GLOBAL de la familia
commit. BUILTIN_SIGNATURES.commit monta commit-settle, cuyo 30% pinta
box-shadow 0 0 0 3px. El commit-set del Slider apunta al PROVIDER, asi que
cada paso de flecha y cada release dibujaban un aro alrededor de la caja
entera (medido en vivo: 544x40). Un aro lee como feedback en un boton y
como borde parasito en un rail ancho y transparente. Exencion acotada en
slider.css — el patron que gradient-builder.css ya practicaba. Alcanza a
todo slider embebido (waveform, media-player, color-picker, time-picker)
porque todos llevan [data-slider]. `animation` es la palanca, no
`box-shadow`: las declaraciones de keyframe ganan a las normales, asi que
solo reiniciar el NOMBRE de la animacion detiene la pintura.
2) EL MISMO ANILLO EN EL PLAYER: commit-complete / commit-fail apuntan al
provider -> borde alrededor de los ~700px del reproductor. Misma exencion
en media-player.css. Los botones conservan su aro pequeno (20x36), que es
donde el feedback pertenece.
3) SALTAR AL FINAL ANUNCIABA UNA COMPLECION FALSA. Los botones de salto no
tienen evento propio: seek() delega a proposito (D-AP2.13, "el release del
scrubber ya ES el commit-set del Slider") — cierto para el scrubber y
VACIO para los botones, que no tienen Slider detras. Al saltar mas alla del
final el elemento dispara `ended` -> commit-complete en el provider ->
sonido (es audible por D-AP2.7) + anillo, mientras el boton de retroceso
quedaba mudo. Saltar al final no es completar: es abandonar la obra en el
final. seek()/scrubTo() marcan el salto que aterriza en el final y `ended`
no anuncia; un `play` posterior limpia la marca, asi que el final natural
se sigue anunciando. Los dos botones quedan simetricos y mudos, que es la
doctrina del transporte.
4) MEDIASESSION SIN OVERLAY DEL SO: el modo audio YA funcionaba (metadata
completa, medida en vivo); el roto era VIDEO, el modo por defecto de la
demo. <AudioPlayer> deriva metadata de title/artist/artwork; el
<MediaPlayer> compositivo no puede leer su hijo Title sin volverse
data-driven, asi que la declara el consumidor — y la demo no lo hacia.
El arreglo es de la demo; el art se comporta como D-SR.6/D-AP2.5 lo firmo.
Guard nuevo (no anunciar compleccion al saltar al final; si al final
natural) VISTO EN ROJO antes de darlo por bueno.
Nota de doctrina, tras leer el corpus a peticion del usuario: la exencion
acotada en la receta ES la puerta, no un olor. motion.md §4 fija que la
firma es generica por decision estructural ("un commit asienta igual en
todo el sistema"), eidos es el UNICO dueno del canal visual, los packs de
sema solo transportan sound/haptic, y el VisualChannel no consulta
activeChannels — no hay palanca declarativa para callar solo lo visual.
Queda anotado, menor: commit-settle escribe 3px a pelo donde sus hermanos
announce-pulse-* usan var(--focus-ring-width).
Verificado: check 75 = baseline, 0 propios; 11/11 media-player;
motion.test.ts verde (la firma global intacta); sonda en Chrome vivo —
slider real -> animation none, elemento no-slider con el mismo sello ->
sigue commit-settle, aro de foco intacto.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
4e4f1b612d |
feat(waveform): F4 eidos + F5 demo — y el buffered pasa a ser la onda
F4 (eidos): wrapper raiz que carga slider.css + waveform.css y resuelve
data-size por ActiveEidos; Wave passthrough; recipe con bars = stroke en
UNIDADES DE VIEWBOX (el svg estira con preserveAspectRatio="none", asi el
ciclo barra/hueco sobrevive) y wave = fill sin stroke; espejo RTL en CSS;
el Slider embebido en scope con track/range transparentes y el thumb
convertido en playhead de 2px a alto completo. Tokens waveform:{height-*,
bar-width, color, color-buffered, color-played, playhead-width} —
playhead-width y color-buffered van ANADIDOS sobre la lista firmada en el
gate, ambos declarados en el README.
F5 (demo + dossier): /uix/components/waveform con picos REALES (fetch de
/sounds/*.wav -> uix.sound.decode -> uix.sound.peaks), cada prop como
control vivo, READMEs de soma y eidos. Fix arrastrado de F3: `dir` no se
reenviaba al Slider embebido.
BUFFERED — cambio de forma (decision del usuario tras ver el render).
El primer intento re-tintaba el SecondaryRange del Slider; el usuario lo
rechazo ("el secundario no puede ser del mismo color") y la auditoria
encontro por que ningun color lo arreglaba del todo: ese part es una BARRA
de 6px con la geometria del track, y sobre una pintura de barras al ~58%
de ciclo el 42% de su area cae sobre fondo desnudo, ademas de tenir el
tramo ya reproducido. Ahora el buffered es una TERCERA COPIA del mismo
path, recortada a su fraccion — es la onda un escalon mas alla, la lectura
de SoundCloud/wavesurfer. El waveform ya no monta Slider.SecondaryRange.
Escalera: color 28% -> color-buffered 55% -> color-played acento.
Guard nuevo (bufferedFraction: ausente sin valor, clampado, dominio
degenerado) VISTO EN ROJO desconectando el cableado antes de darlo por
bueno.
Verificado: check 75 = baseline, 0 propios; 5/5 soma + 4/4 morfo;
component:audit --only waveform PASS; eidos-lint 0 invalidos;
recipe-css-contract y motion.test verdes; docs:check 0; prettier limpio.
Queda F6 (mirada del usuario): el panel del navegador no composita, asi
que la verificacion fue por sonda del DOM, sin captura.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
cd9b06fa2f |
docs(blocks): auditoria del tier congelada — 29 confirmados, 50 sin verificar
Auditoria de los 15 blocks con 50 agentes: dos dimensiones en paralelo —doctrina
(contrato B + regla de coordinacion, por lectura) y percepcion (sema en vivo en
navegador, interaccion por interaccion)— y despues un verificador ESCEPTICO por
hallazgo, con el encargo de refutarlo y la lista de falsos positivos conocidos
del repo.
90 en bruto → 40 verificados → 29 CONFIRMADOS + 11 refutados (28%)
Ese 28% es la razon de existir de la fase adversarial: sin ella habrian entrado
once acusaciones falsas.
⚠️ 50 hallazgos quedaron SIN VERIFICAR, 18 de ellos marcados ALTA. El tope de 40
lo puso mi script, no el trabajo. No son menos graves: estan sin comprobar.
`AUDIT-blocks-2026-08-01.md` es la UNICA copia — el journal del workflow era de
sesion y ya no existe. Incluye los refutados con su motivo para que nadie los
vuelva a reportar.
Lo gordo confirmado:
- `contact` importa `$libs/forms`, fuera de la lista de la frontera dura 1.
- El nombre accesible del submit de `contact` esta congelado en «Enviar» en los
cinco estados: `Form.Submit` impone su `aria-label`, asi que las palabras de
estado que el block existe para poseer NO llegan al lector de pantalla. El
block cumple su promesa solo a la vista.
- `site-header` congela la pagina: con el cajon abierto, cruzar el `breakpoint`
esconde cajon/overlay/trigger con `display:none` pero deja el Drawer abierto y
el scroll del body bloqueado. Reproducido con swipe tactil real rotando un
telefono; la unica salida es Escape, que en tactil no existe.
- `hero` en `layout="background"`: CTA secundaria a 1.78:1, bajo AA, y el token
que la demo escribe a mano para arreglarlo es inerte.
- `height="100%"` sobre `Card` es INERTE (atributo HTML, no prop) en `pricing` y
`testimonials`: las tarjetas no igualan alto mientras README y demo afirman lo
contrario.
- B-8 sin declarar (landmark + jerarquia de encabezados) en seis READMEs.
La dimension CRUZADA —los 15 en una pagina compuesta— NO se ejecuto, y es ademas
la condicion de cierre de F2 del plan.
La auditoria confirmo su propia premisa: la capa perceptiva no se habia ejercido
NUNCA porque la galeria arrancaba sin packs de sema desde F0, y ahi esta lo mas
feo (todavia sin verificar): el nav del header emitiendo `commit-select`+`affirm`
con earcon audible al pasar el RATON por encima, un `Enter` en `newsletter`
emitiendo dos `commit-submit` con intents contradictorios, y 4 de 7 elementos del
header mudos.
El handoff `CONTINUE-blocks.md` arranca ahora por la auditoria, con el orden
recomendado (verificar los 50 → arreglar por severidad → pagina compuesta →
catalogo → v2) y la deuda declarada del arnes, que triplica la cadena de arranque.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
0ab6cc822c |
feat(waveform): F1-F3 — sound.peaks, morfo delegado y soma componiendo Slider
Waveform (gate D-WF.1-8 firmado, PLAN-waveform.md): la visualizacion de audio cuyo contrato es el DATO y su pintura — la interaccion ES el Slider canonico. - F1: uix.sound.peaks(buffer, buckets) en el art — max absoluto por bucket sobre todos los canales, normalizado por el bucket mas alto (documentado); puro, sin contexto. El flujo natural decode -> peaks en la misma puerta. - F2: morfo con DOS partes (provider con data-shape/data-readonly; wave svg presentacional aria-hidden), SIN campo events y expression 'delegated' — re-declarar drag/commit seria la duplicacion que D-AP2.13 retiro del player. El part Cursor del borrador, PODADO como especulativo (el thumb del Slider embebido ES el playhead). - F3: WaveformProvider con la geometria testeable (buildWavePath: bars = trazos verticales con piso perceptivo 0.5 — el silencio pinta linea fina, no hueco — y ancho de barra via stroke-width de la recipe; wave = area() de $libs/plots espejada) + progressFraction clampada; wrapper que compone el Slider COMPLETO (SecondaryRange G-2 + Range + Thumb, con valueText G-1 y readonly/disabled/step reenviados); Wave con el clip de progreso (dos copias del mismo path, hooks eidos-only -played/-remaining). Verificacion: art+port 42/42 - morfo 4/4 - soma 4/4 - check 0 propios en los arboles del waveform - vocabulario limpio. Quedan F4 (recipe eidos + tokens, disenada en el CONTINUE), F5 (demo+dossier+audit) y F6 (mirada del usuario). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
96393e4305 |
docs(blocks): cada block declara su posicion bajo la doctrina de coordinacion
Y `content-section.Media` pasa a seguir al texto por defecto. DEFAULT: `.Media` era `width='wide'`, asi que una figura se salia de la columna SALVO que dijeras lo contrario. Al reves de lo que hacen todas las referencias (en `@tailwindcss/typography` una imagen dentro de `prose` queda capada a la medida y salirse exige el truco del `translate`; en `markdown-body` de GitHub, `max-width: 100%` de la columna). Ahora el default es `measure` — la figura sigue al texto y el escape se PIDE. Cambiado en los cuatro sitios que decian lo contrario: la parte, la ruta `preview`, el estado inicial del control de la demo (una demo que pisa el default en silencio ensena algo que el block no hace solo) y la doc. Verificado: 528 = ancho del texto exacto a 1011/1280/1920, y `wide` / `full` siguen funcionando cuando se piden. DOCUMENTACION: los 15 READMEs declaran ahora su posicion bajo §«Coordination», y el mapa real no es uniforme — - Coordinan: `contact` (maquina + esquema + palabras) y `pricing` (el periodo por contexto). Pero en pricing nada se puede BLOQUEAR, asi que no necesita maquina ni palabras: coordinar no es siempre una maquina. - Comparten CONFIGURACION, no estado: `team` (`align`) y `content-section` (la medida). - Costura bindable hacia el canon: `faq` (`value` → `Accordion`) y `site-header` (`mobileOpen` → `Drawer`). Reenviar no es poseer. - No poseen nada: banner · cta · feature-grid · feature-split · hero · site-footer · stats-band · testimonials. Decirlo explicitamente es el punto: inventarle estado a un block que no coordina nada es el error contrario. - `newsletter` queda como CANDIDATO ABIERTO: coloca un envio que puede quedar bloqueado sin que nada diga por que — el `disabled` lo monta el app en el punto de uso — que es exactamente el hueco que cerro `contact`. Es anterior a la doctrina (block del 30-07, doctrina del 31-07). Registrado, no asumido: se toca con decision del usuario. El README del tier apunta a la doctrina y el handoff recoge el mapa completo. Gates: blocks:check verde (15) · docs:check 0 · svelte-check sin errores en lo tocado · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
378457f46c |
feat(blocks): content-section cierra F2 — la rejilla de escape, y las escalas tipograficas salen de Text
El ultimo block de sitio, y el que mas cerca estuvo de ser un envoltorio:
`Section` + `Container` + `Prose` no habria anadido nada, porque `Prose` ya trae
su medida de lectura. Lo que falta en el ecosistema es el ESCAPE — dentro de un
`Container` nada puede ser mas ancho que el, asi que una figura a sangre hay que
sacarla del articulo y ponerla de hermana, y el orden de lectura se rompe.
La seccion es una rejilla de 5 pistas (canalon · flanco · MEDIDA · flanco ·
canalon) y cada parte dice hasta donde llega: `measure` col 3, `wide` col 2/5,
`full` col 1/-1. Todas las longitudes son tokens del sistema (`--measure-*`,
`--container-width-*`, `--container-padding-inline`). `.Body` y `.Media` son
compound porque SE REPITEN; la cabecera son slots de snippet. `.Body` apaga la
medida de `Prose`: dos duenos del mismo ancho se pelean y ganaria el mas
estrecho en silencio.
Los tres ejes son RESPONSIVE (`measure`, `wide`, y el `width` de cada parte),
resueltos con `eidos.resolve()` — el mismo camino que usan los primitivos de
eidos, asi que los unicos breakpoints en juego son los canonicos. Es lo que un
valor unico no puede decir: `{ base: 'full', md: 'wide' }` = foto a sangre en
movil y contenida de `md` arriba.
CANON: un componente no es libreria de otro. `Heading` importaba CINCO escalas
tipograficas de `Text` (`TextTracking`, `TextLeading`, `TextWrap`,
`TextNumeric`, `TextMeasure`) y al tipar este block repeti la violacion. Las
cinco se mueven a `eidos/lib/types.ts`, que es donde su propio encabezado dice
que van y donde ya esta documentado el precedente del mismo fallo (`ColorRole`
re-escrito a mano que derivo a 8 miembros contra 9). Sin shim de compatibilidad:
`text` y `heading` las importan de la libreria.
Cuatro defectos que solo dijo el navegador:
1. `gap` separa tambien las COLUMNAS y una parte que las cruza se lleva esos
huecos encima — `wide` media 1088 en vez de los 1024 del contenedor con el
que debe alinearse, y a 420px `full` llegaba a 532 dentro de una rejilla de
404 y hacia scrollear la pagina. Van `rowGap` + `columnGap={0}`.
2. Una pista `min(medida, 100%)` ignora sus propios canalones: a 420px la
central se quedaba los 404 enteros y la rejilla se iba a 436.
3. Un token de container es un ancho EXTERIOR: con `--container-width-lg` a
pelo la figura `wide` sobresalia 16px por cada lado respecto al texto de la
seccion hermana de debajo, a 1280/1440/1920. La pista ancha resta ahora
`2 * --container-padding-inline` (992 en `lg`) y el desfase es 0. Lo cazo el
usuario mirando la pantalla: yo habia medido el block contra si mismo, no
contra la pagina.
4. `100%` significa algo distinto dentro de cada parte — en `measure`/`wide` ya
es una pista, en `full` es la rejilla entera con canalones —, asi que capar
el pie igual en los dos casos lo dejaba a 340 contra una columna de 372 en
uno y a 404 en el otro.
Verificado en navegador: barrido 375→1920 sin desbordamiento; alineacion
pie↔prosa identica en 3 viewports × 3 medidas × 3 anchuras (18/18); figura
`wide` a ras del texto de la seccion hermana en 1280/1440/1920; y el valor
responsive cambia exactamente en el `md` canonico (a sangre a 767, contenida a
768). Claro, oscuro, 420px y 1920 mirados, y la geometria confirmada tambien en
el Chrome del usuario.
Semantica propia `figure`/`figcaption` (no interactivos → estructura de
documento, B-8) con el hueco senalado: el canon no tiene primitiva `Figure`.
Contexto de CONFIGURACION, no de estado — es un block de layout y no coordina
nada.
Gates: blocks:check verde (15 blocks) · svelte-check sin errores en lo tocado ·
eidos 28/29 suites (la que falla es `audio-player` sin morfo, del hilo de audio,
ajena) · docs:check 0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
5d2816a0c4 |
fix(sema): silencio del player ampliado a los sliders — pick/drag por canal
Escucha real del usuario tras D-AP2.7 v2: los botones ya callan, los sliders no — sonaban el pickup del agarre y el feedback continuo del drag (el commit-set del soltar ya estaba restado). Dos reglas de descendientes con channels: ['haptic'] para handle-pick y handle-drag de los Sliders compuestos (scrubber y volumen): ambos son eventos ESTRUCTURALES sin intent — el libro concentra la evaluacion en el drop, cubierto por la resta de commit-set — asi que desactivar su canal sonoro dentro del player no borra carga evaluativa (la regla que prohibe tirar canales protege intents, y handle no lleva ninguno). El resolver por-emit del drag sigue poseyendo las primitivas del sonido, pero una senal con el canal inactivo no suena; el tick tactil se conserva. El pickup lleva primitivas absolutas de libreria (gain 0.12), por lo que una resta se acoplaria a su valor — el canal es la via limpia. Corrige la nota del pack («no callable desde el pack era conservadora de mas») y el plan v2 (D-AP2.7 v2 ampliada). Fuera del player los sliders conservan su firma completa (selectores de descendientes, especificidad 30 sobre 20 del pack del slider). Verificado: morfo:vocabulary limpio - sema 192/192 - check 74 = baseline, 0 propios. El verify final es la escucha: agarrar y arrastrar el scrubber debe ser mudo (solo tacto), y fuera del player un Slider suelto debe seguir sonando como siempre. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
85b269f745 |
feat(sema): transporte del player silencioso por defecto — D-AP2.7 v2
Firmado 2026-07-31: el player es el unico componente cuyo output ES audio — un tick de UI en su transporte compite con la obra por definicion (D.7). El pack aplica el patron D.5 (resta exacta del gain de familia; las primitivas del intent intactas, threat/fulfill afloran) a todo lo que dispara un gesto: - commit-toggle-play / commit-toggle-mute / commit-set-rate en sus botones; - el contact-activate de los Buttons compuestos (regla de descendientes, H-10; tuning nuevo `contact.silent` -0.25 en sounds.ts) — IconButton compone Button sin morfo propio, asi que [data-button] casa (verificado); - el commit-set de los Sliders compuestos (scrubber y volumen). Audibles se quedan: commit-fail (el risk aflora sobre la resta) y commit-complete (informa de la obra; revisitar con escucha real). El handle-drag continuo conserva su resolver por MECANISMO (el payload por-emit posee las primitivas — el propio pack del slider lo declara); callarlo seria iniciativa de resolver/canal. La voz se reactiva en la capa de app (cascada 5b), sin prop nueva. Complementario con duckUiWhileContent: el pack calla AL PLAYER, el duck atenua al RESTO de la app. Haptica se queda. Selectores 100% del builder tipado (solo el combinador descendiente es literal). Verificado: morfo:vocabulary limpio - sema 192/192 - check 74 = baseline, 0 propios. Auditoria posterior: regla de descendientes confirmada viva (IconButton -> Button), sin regla rival de commit-set en el pack del slider, deltas componen por el mecanismo D.5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
c4d64ba98a |
feat(media-player): modo audio de referencia — AudioLayout, <AudioPlayer> con 4 variantes y buffer real (F4-F7 parcial)
El modo audio deja de ser "video colapsado" (las 3 reglas CSS de v1):
- F4 sema: el docblock del pack escribe la doctrina D-AP2.7 — el silencio
mientras suena la obra lo posee el SERVICIO (duckUiWhileContent, receta
documentada); la resta-de-gain queda disenada sin cablear a proposito.
- F5 buffer real (G-2 consumido): el TimeSlider soma pasa bufferedTime como
secondaryValue del Slider compuesto y monta Slider.SecondaryRange; se
retiran el div .mp-buffered, la var --media-buffered (y su writeProperty),
--_mp-buffered y --_mp-rail. Look intacto: el 42% pasa a
--slider-secondary-bg en scope.
- F5 wrappers eidos de las 6 partes: Artwork compone AspectRatio+Image del
catalogo via child; RateButton compone Button ghost mostrando {data-rate}x;
Artist/Identity/Transport/LiveIndicator passthrough.
- F5 AudioLayout: espejo de DefaultControls para audio — identidad
(artwork+title+artist+live) + transporte + fila de scrubber + secundarios.
- F5 raiz competidora <AudioPlayer> (eidos/components/audio-player/, el
precedente Toast/Toaster): una etiqueta monta todo, y la metadata del SO se
DERIVA de la identidad visible (title/artist/artworkSrc) salvo override —
lo que ves es lo que muestra el lock screen (D-AP2.5).
- F5 recipe de 4 variantes (card/row/bar/inline) scoped a [data-variant]
(componer a mano sigue sin opinar) + tokens publicos --audio-player-*
(gap, artwork-radius, artwork-size-row/bar), regenerados.
- F7 parcial: el badge live usa el text localizado del morfo via soma (fuera
el "LIVE" hardcodeado) · demo con el modo audio real (<AudioPlayer> +
selector de variante + snippet con paridad) · README eidos con la seccion
del modo audio · component:audit --only media-player PASS.
Verificacion: player 18/18 · recipe-css-contract + component-api-contract
37/37 · eidos-lint media-player 0 invalidos · check 74 = baseline, 0 propios
(demo incluida). Declarado: audio-player.css es recipe de composicion sobre
el morfo del player (el lint per-component no la cubre — gap del tooling);
los internos --_mp-* los comparte la recipe hermana del MISMO morfo.
Quedan F6 (verificacion VISUAL de las 4 variantes — exige ojos) y el resto
de F7 (medicion Chrome del escenario mixto + teclas de medios + stamp sema).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
2f872ef864 |
feat(sound): $sound orquestador (buses+voces+media) y player v2 F1-F3 sobre el servicio
Rediseno clean-room del sonido (gate D-SR.1-12 firmado; la doctrina "la voz
es del art" de PLAN-sound-engine s16.1 queda revocada):
- Mezcla: master -> buses ui/content (gain/mute/duck refcount, gana la mas
fuerte; politica pre-contexto). prefs de UI jamas tocan la obra.
- Voces registradas: la calibracion es DATO del consumidor (sema registra
'sema'; el default conserva la calibracion historica - cero cambio audible,
A/B del usuario: "suena igual").
- Ciudadania de media: sound.media(el) PROVEE el transporte (la forma exacta
del puerto MediaProvider) + foco mixed/exclusive/duck PERSISTENTE (estado
derivado; el ducker mas reciente retiene la palabra) + duckUiWhileContent +
MediaSession de la fuente activa (play/pause/seekto + posicion, liberacion
y promocion) + visibilidad content-aware + attach() opt-in (irreversible,
CORS declarado). Re-registro idempotente por elemento.
- diagnostics.ts con catalogo tipado (contrato de arts) y guards post-dispose
(ningun camino abre un contexto huerfano). Auditoria AU-1..9 resuelta;
bundle medido: 16,2 KB min / 5,6 KB gz.
Player v2 (gate D-AP2.1-13 firmado) F1-F3:
- F1 gaps de framework: aria-valuetext en Slider (valueText por thumb) +
parte SecondaryRange con token --slider-secondary-bg (buffer/clip/capitulos)
+ formatDuration en $libs/days + fix del selector del pack (H-1: la regla
de play/pause construia sobre provider y el stamp aterriza en play-button).
- F2 morfo: 6 partes audio-only (artwork/artist/identity/transport/
rate-button/live-indicator), commit-set-rate, commit-set-time RETIRADO
(D-AP2.13: el scrubber delega en el commit-set del Slider compuesto),
sustain-loading cableado (H-9), teclado j/l, </> y 0-9 (con guard de
modificadores para no pisar atajos del navegador).
- F3 la migracion: el transporte default es uix.sound.media(el, {metadata})
(pineado con el motor real); nativeMediaProvider RETIRADO (media-provider.ts
queda como contrato puro del puerto, sin shim); G-5: los commits de
play/mute/rate cabalgan el evento de RESULTADO (play/pause saltando el
pause de ended, volumechange con transicion de muted, ratechange);
formatTime -> formatDuration; sub-providers + wrappers + exports de las 6
partes; prop metadata publica (captura al registrar, documentado).
Verificacion: 45 suites / 458 tests verdes en el alcance (sound, sema,
active-uix, active-app, morfo, slider, media-player, days) - check 74 =
baseline de la rama, 0 propios - docs:check 0 - invariante de UN AudioContext
intacta (e2e sin tocar). Planes de proceso y handoff al dia
(PLAN-sound-redesign, PLAN-audio-player-v2, CONTINUE-sound-engine).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b1b568e32f |
feat(blocks): contact CERRADO — la coordinacion entra en el contrato B
El estado se le pregunta al ESQUEMA, no al Form. `form.isValid` con `validationBehaviour: 'progressive'` significa «aun no se ha encontrado nada mal»: un formulario vacio e intacto no tiene errores, asi que se declaraba valido y `incomplete` era INALCANZABLE al cargar — la seccion pedia resolver la verificacion con los tres campos vacios, justo el hueco que la doctrina existe para cerrar. Ahora la raiz valida los valores con `validateSync` de SIUM: sincrono y sin escribir errores (`form.validate()` habria encendido los tres campos en rojo en el primer frame). `state.test.ts` fija la maquina con 9 casos — primer test unitario del tier, porque `state.ts` es su primera logica pura: el orden de prioridad, que solo `ready` deja enviar, y que todo estado bloqueado tiene frase. Contrato B (architecture/blocks.md): seccion «Coordination» con las tres cosas que posee un block que coordina (estado, forma de sus datos, palabras), B-5 admite el esquema por defecto, B-7 enmendado (posee las palabras de SUS estados como idlangref con fallback ingles) y la convencion de servicios acotada: el traductor es el UNICO servicio sancionado. Dice tambien a quien NO aplica — los blocks de layout no coordinan nada. i18n por la via real: el arnes registra el namespace `blocks` en las DOS cadenas de arranque (galeria y previews, que duplican el boot) y la demo se lee en castellano en vez de caer al fallback ingles. Ademas: README y pestanas de doc rehechos al reparto nuevo, ficha F2.13 y bitacora en PLAN-blocks.md, y el control vivo que faltaba para un prop publico — `verification` con reto / sin reto, porque omitir el prop no es lo mismo que pasar un estado que nunca verifica. Verificado en navegador, arco completo y las dos ramas: sin reto vacio → un campo → tres campos (se desbloquea) → «Enviando…» → «Enviado»; con reto, con los tres campos llenos el boton sigue bloqueado y la razon pasa a «Resuelve la verificacion de arriba». `verifying` y `rejected` quedan cubiertos por test unitario, no por el reto en vivo: el provider de ProofOfHuman escribe `status` el mismo y el veredicto del app no se sostiene (hilo de canon abierto). Gates: blocks:check verde (14 blocks) · vitest 9/9 · docs:check 0 · svelte-check sin errores propios · prettier limpio en los ficheros propios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
75c2602591 |
feat(blocks): `contact` — el primer block que COORDINA (estado, datos y palabras)
WIP declarado: el block funciona y renderiza, pero el arco interactivo completo no está verificado todavía. Lo que falta está en `CONTINUE-blocks.md`, en orden. ## El cambio de doctrina Decisión del usuario, textual: «si al final es una lista de componentes… el bloque es coordinación, estado, y data también». El motivo se veía en este mismo block: con el estado fuera, cada `disabled` era una expresión distinta montada en el punto de uso, y aparecían huecos — un envío que no se podía hacer y nadie decía por qué. Eso cambia lo firmado en `docs/architecture/blocks.md` (B-5/B-7, D-BLK). Queda ANOTADO como pendiente de escribir en el contrato: propagarlo sin actualizarlo sería deriva. ## Lo que ahora posee el block - **La máquina de estado** (`state.ts`): `incomplete · unverified · verifying · rejected · ready · sending · sent`, derivada de la validez del `Form`, del veredicto de verificación y del envío. Con dos registros EXHAUSTIVOS — `CONTACT_REASON` y `CONTACT_ACTION`— que hacen imposible un estado mudo: no se puede añadir un estado que bloquee el envío sin darle su frase, porque el `Record` obliga. - **La forma de los datos** (`schema.ts`): nombre · correo · mensaje, con las etiquetas como idlangref. El app lo sustituye pasando su `form` + `schema`. - **Las palabras**, por el traductor (`#?blocks.contact.…|fallback`), la misma puerta que usa el canon para sus `texts:`. Partes nuevas: `Contact.Fields` (los tres campos del esquema propio), `Contact.Submit` (bloqueo Y etiqueta desde el estado, nunca desde una expresión en el punto de uso) y `Contact.Reason` (la frase del estado). `Contact.Form` toma el handle del contexto en vez de pedirlo otra vez. La demo adelgaza en consecuencia: se le cayeron el esquema, las etiquetas, el `disabled`, el flag de envío y la frase. Le queda lo que es de un app — las palabras de la página, los datos de contacto, el reto que elige y qué significa «enviar». ## De paso, dos arreglos de composición - El `TextArea` iba dentro de `Field.Control` y salían DOS marcos: pinta su propia carcasa (borde, radio y relleno son de su recipe). `Field.Control` es la carcasa del `Field.Input` desnudo. - El `Form.ErrorSummary` vacío dejaba una caja naranja: su raíz pinta borde de riesgo en cuanto el form es inválido, aunque la lista esté vacía. ## Dos hilos de canon abiertos (medidos, sin causa raíz) - Con `progressive`/`onSubmit`, un envío inválido bloquea y mueve el foco pero no expone mensajes; con `onChange` sí, pero saltan los tres campos al escribir en uno. La máquina tapa el agujero de cara al usuario; el hueco sigue. - El provider de `ProofOfHuman` escribe `status` él mismo, así que el veredicto que devuelve el app no se sostiene y los slots `stage*` no renderizaron nunca. Gates: `blocks:check` verde (14 blocks) · `svelte-check` sin errores propios · prettier limpio. Verificado que la sección renderiza (section · form · 3 campos · submit · cero errores de página) en un dev server limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
2de1718c3b |
refactor(langs): words se pliega en palabras — y el nombre sale del corpus
El pack words desaparece: sus strings de motor entran en palabrasLangs y las guardas (ACTIVE_DEV_TRACK, WIP_TRACKS) dejan de listar 'words'. Las menciones en comentarios de recetas, READMEs, SPECs, demos y docs de proceso pasan a Palabras. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
2 months ago |
|
|
b48264902f |
docs(process): el gate del reproductor, consolidado — se presenta una vez y no se mueve
La sesión perdió convergencia: tres presentaciones del gate lo movieron tres veces (D-AP.7 corregida dos veces, D-AP.12 → v2, D-AP.13 apareció al final), y de sus commits los últimos corrigieron a los anteriores. Este pase consolida TODO en el cuerpo del plan para que la sesión siguiente firme en frío, sin re-excavar. Consolidación pura: sin código y sin hallazgos nuevos. - **D-AP.7, criterio final: incongruencia, no frecuencia** (D.7: «si el contenido ES audio, un tick de UI compite con la obra»). Con frecuencia como criterio, play/pausa —2 a 10 veces por escucha— quedaría fuera y la línea sería arbitraria. Se cancela el gain de lo que suena MIENTRAS suena la obra; se conserva lo que informa del estado de la obra (`commit-complete`, `commit-fail`), con `commit-complete` anotado como el discutible de los dos. - **D-AP.13, nueva**: `commit-set-time` duplica el `commit-set` del `Slider` embebido (`onValueCommit` → `seek()` → `trigger`, dos líneas leídas; el del Slider, medido en navegador). El rationale de radio-cards aplica literal, y el propio provider ya practica la delegación en `setVolume`. Recomendación: retirar y delegar; el coste va a F2 como cambio de contrato declarado. - **§8.7 — la postura de participación, resuelta**: `expression: 'pack'` es correcta (los eventos de ciclo de vida no los posee ningún hijo, y el guard S11d la exige mientras exista el pack); el player es un híbrido legítimo con UNA duplicación (D-AP.13) y UN defecto de timing (G-5). La premisa de D-AP.1 queda intacta — la pregunta que la última pasada dejó como abismo tenía suelo en la doctrina. - El clip queda condicionado a D-AP.12(b) en §5, F3 y F5, coherente con la recomendación v2; F2 recoge D-AP.13 y la ejecución de H-9. - La cabecera fija la regla de cierre: la cuarta presentación **se firma o se rechaza, no se re-excava** — y el kickoff de sesión nueva lo dice explícito. - El handoff (`CONTINUE-sound-engine.md`) queda en «firmar, sólo firmar», con la trampa que costó la sesión escrita donde las demás: **una medición no es un diagnóstico** — leer el contrato de la pieza (README → CANON §8 → book-deviations → código) antes de acusarla. `docs:check` 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
061d66299b |
fix(docs): D-AP.7 proponía un mecanismo que viola la regla dura de la cascada
Leyendo `book-deviations.md` D.5/D.7 —que llevo dos días citando sin abrir—
resulta que el mecanismo que propuse para el silencio en modo audio está mal, y
que el correcto ya estaba canonizado en el catálogo.
Proponía `channels: ['haptic']`: tirar el canal sonoro entero bajo
`[data-media-player][data-media='audio']`. Eso **contradice la regla dura que
está en `CLAUDE.md`** — *«Cascade rules add character, never the intent's
evaluative profile… to shift one, compose with `{ op: 'add', value: … }`»* — y
su consecuencia práctica es peor que el problema: borra también la carga
evaluativa, así que un `commit-fail` dejaría de oírse justo cuando hace falta.
El patrón correcto ya existe y está canonizado como D.5: `form.toggle.silent`
(`gain: { op: 'add', value: -0.3 }`) y `tooltip.silent` (-0.2) **restan
exactamente el gain de la familia**, de modo que lo cotidiano cae a 0 y los
deltas de intent siguen aflorando. Su propio comentario lo dice: «avoids fatigue
without erasing evaluative loading».
Aplicado al player, con una precisión que obliga un dato medido: **`risk` no
añade gain** (sólo `threat` +0.1 y `fulfill` +0.05), así que una cancelación
global también silenciaría el fallo de carga, que es `commit-fail + risk`. Por
eso la regla cancela el gain **sólo en los eventos frecuentes del transporte**
—`commit-toggle-play`, `commit-set-time`, `commit-toggle-mute` y el
`contact-activate` de los botones compuestos— y **deja intactos**
`commit-complete` y `commit-fail`, que son ocurrencias, no frecuencia.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
81378a6875 |
fix(docs): H-10 mal diagnosticado — el canon separa las dos voces, el player las junta
Cerré H-10 hace un commit diciendo que el doble earcon era «el patrón de composición del catálogo» y que la decisión —regla nueva en el árbitro, o canon que declarase el solape intencionado— era materia de `book-deviations.md`. Es al revés: **el canon ya lo prescribe, y quien lo incumple es el media-player.** El README del `Button` lo dice citando el libro (cap. 22 §8 y §10): `contact-activate` es `sequence: 'pre'` y sólo acusa la RECEPCIÓN del gesto; la consecuencia evaluativa la dispara el consumidor **cuando el resultado aterriza** — *«the celebration sound plays at the moment the save actually resolves, not when the click is received»*. Dos sonidos, sí, pero **separados en el tiempo** y con dos significados. En el diseño no hay solape. El solape lo mete el player: `togglePlay()` dispara `commit-toggle-play` síncronamente dentro del handler del clic, no cuando el medio arranca — y el resultado real llega después, en el evento `play`/`pause` que el provider YA recibe (ahí arranca el bucle de progreso, y `commit-complete` ya cabalga `ended` con ese patrón exacto). Mismo defecto en `toggleMute()` y `seek()`; `toggleFullscreen` y `togglePip` lo hacen bien, esperando a la plataforma. Consecuencias: - H-10 deja de ser materia de canon y pasa a **G-5** en §8: mover el commit del clic al evento de resultado, aplicando el patrón que el propio fichero usa. - **El árbitro de dominancia se queda como está.** Nunca tuvo que arbitrar dos ocurrencias que el diseño no quiere simultáneas; proponer una regla nueva era arreglar el síntoma en la capa equivocada. - El test conserva la medición (dos ocurrencias en el mismo tick = dos earcons, 4 osciladores) porque el dato es correcto y vale como guardia; lo que se reescribe es su lectura. Para D-AP.7 no cambia lo demás: ninguna de las dos señales vive en el provider, así que el silencio en modo audio tiene que cubrir descendientes. sema e2e 5/5 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
5ac194cfe1 |
test(sema): un botón compuesto suena DOS veces, medido (H-10)
En F0 observé que el botón de play estampa dos señales por clic. Afirmé de ahí que sonaría dos veces, y eso era un salto: el motor tiene un árbitro de dominancia precisamente para esos casos. Medido: **4 osciladores, dos earcons**. Por qué el árbitro no los colapsa. `occurrenceRank = evaluable×10 + activación`: `contact-activate` del `Button` es estructural y sin intent → **0**; `commit-toggle-play` del player es evaluable con intent `neutral` → **10**. La regla silencia a un recién llegado sólo si una ocurrencia YA ACTIVA lo supera, y aquí llegan en orden 0 → 10: cuando entra el segundo, el activo no lo supera; y el primero ya había sonado. El modelo asume que la señal de más rango llega antes o sola — **el caso inverso, a microsegundos, no está arbitrado**. Tampoco hay pack de `button`, así que `contact-activate` usa la base de familia (800 Hz, 60 ms) y se solapa con el commit de 100 ms que entra encima. **No es del media-player**: es el patrón de composición del catálogo — la primitiva acusa el contacto, el componente confirma el valor. Cualquiera que componga `Button` y dispare su propio evento suena dos veces con el sonido activo. El test lo pinea donde se puede oír el camino entero (`sound-e2e.test.ts`), y la decisión —regla nueva en el árbitro, canon que declare el solape intencionado, o packs que silencien `contact` bajo componentes que hablan por sí mismos— queda fichada en el plan como materia de `book-deviations.md`, no de una sesión. Para el reproductor cambia una cosa y es la que importa para D-AP.7: el silencio en modo audio tiene que cubrir las DOS señales, y ninguna vive en el provider. sema + art 17 suites / 205 tests · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
7ae5a8117d |
docs(process): F0 del reproductor — las verificaciones, hechas antes de pedir la firma
La mitad de F0 que no necesita firma, ejecutada primero, porque tres de los cinco resultados cambian lo que hay que firmar. Sin código del player. - **H-1 confirmado con stamp real.** `data-event="commit-toggle-play"` aterriza en `<button data-media-player-play-button>`; el pack selecciona el provider y no puede casar. Es **1 de las 3 reglas** —`commit-complete` y `commit-fail` apuntan bien—, y el fix de una línea **cambia comportamiento** (empieza a sonar donde no sonaba), así que espera a la firma como G-4. - **El `Slider` compuesto sí sonoriza**, por composición y sin excepción: `TimeSlider` → `Slider.Provider` → `resolveSliderDragSound` en cada `handle-drag`. Stamp medido sobre `[data-slider]`. La regla propuesta funciona porque `Element.matches()` evalúa descendencia. - **Hallazgo nuevo**: el botón de play emite **dos** señales por clic — `contact-activate` del `Button` compuesto y `commit-toggle-play` del player, ambas en el propio `<button>`. Una regla de silencio anclada al provider no alcanza a ninguna: el silencio en audio tiene que cubrir los DESCENDIENTES. - **H-9 — `sustain-loading` es superficie de contrato muerta.** La pregunta de F0 («¿se auto-limpia?») era inválida: **no se emite nunca**. Está declarado en el morfo, verificado por su test y documentado en el README de soma como si viviera; no hay un solo `trigger`. El spinner lo mueve `data-buffering`, que es estado. `sustain-end` queda descartado, y qué hacer con el evento —cablearlo o retirarlo— entra en el gate. - **Segmentos: 1 de 6, no paridad.** Media Chrome no tiene componente de clip en su `main` (revisados `src/js` y `experimental`; el `media-clip-selector` que circula en buscadores es de una 0.x retirada), Plyr sólo `markers`, Red Hat no lo documenta, react-h5 no lo lleva. Sólo wavesurfer, por plugin. **D-AP.12 cambia de recomendación: v2**, con el diseño de §7.5 congelado. Diferirlo no acorrala —sin `clip` el dominio es `0…duration`, añadirlo luego es un modo nuevo, no un cambio de significado. - **vidstack#1195 caracterizado, NO reproducido**: abierto, sin respuesta, iOS 17.3.1 en Chrome y Safari móviles, escritorio sano, `webkit blob resource error 1`. Apunta a fuentes `blob:` + su forma de acotar el rango seekable, no a la idea de clip; nuestro diseño no toca la fuente. Pero *probable* no es *verificado*: exige un iOS real, y este entorno no lo tiene. Correcciones de método aplicadas al propio trabajo: `gh` no existe en este entorno y un `|| echo` lo convirtió en un falso negativo sobre media-chrome — rehecho por la API. Y la recomendación de D-AP.12 se cambia con los datos delante en vez de defender la que ya estaba escrita. `docs:check` 0 errores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d2e841c65d |
fix(sound): la auditoría del art, cerrada — niveles por reproducción y opciones que no mienten
Los tres hallazgos que quedaban de `PLAN-sound-engine` §16, firmados y aplicados.
Dos con código, uno con una medición que cambia la doctrina.
**A-5 — `gainScale` es de la reproducción, no del documento.**
`play()` escribía `master × gainScale` en el nodo master, del que cuelgan TODOS
los earcons. Un parámetro transitorio corrompía un mando de política: el nivel
se filtraba entre reproducciones, una nota en vuelo saltaba de volumen, y los
dos consumidores que motivan el motor compartido —la reducción de sema y el
ducking de un reproductor— se pisaban sobre un único valor. Ahora el factor
multiplica el pico de la envolvente de ESA reproducción, y **también la
profundidad del AM**: dejarla absoluta habría hecho que un earcon atenuado
sonara relativamente más áspero, cambiando el timbre en vez del volumen, que es
lo contrario de lo que significa una reducción.
**RC-5 se reescribe, y es legítimo**: un contrato de regresión protege
comportamiento, no defectos. Lo que pineaba —«la ganancia se fija ANTES de que
la síntesis reviente»— sólo importaba porque el valor iba a un nodo compartido.
La cobertura no se pierde, se coloca donde vive cada responsabilidad:
`sound-port.test.ts` ya pineaba que el canal resuelve el NIVEL y lo entrega;
`sound.test.ts` pinea ahora que el master **no se mueve**; y el art estrena un
contexto falso que sí construye el grafo, para ver el pico de la envolvente.
**A-4 — las opciones del canal, imposibles de equivocar.**
`SoundChannelOptions` pasa a unión discriminada: `{ engine, preferences?,
logger? }` **o** la forma de construcción (`audioContextFactory` / `fetcher` /
`dom` / `masterGain` / `timers`). Nunca ambas — un motor llega ya construido, así
que sus opciones de construcción no significan nada a su lado, y hasta hoy se
aceptaban y se tiraban en silencio. La defensa primaria es el tipo (doctrina de
la casa), con un guard `@ts-expect-error` que hace fallar `check` el día que la
unión deje de rechazar la mezcla; el aviso por logger es la red para JS.
`engine.ts` pasa de un spread a tres ramas en orden de precedencia.
Y **`fetchFn` → `fetcher`**, para alinearse con `$perm`: un nombre por concepto.
`audioContextFactory` **se queda** — es el patrón `idFactory` que ya usan
`$logger` y `$bus`, y nombra el tipo exacto que fabrica (`context` a secas
colisiona con el contexto GL de `$scene`).
**A-3 — S-1 aceptado, medido: 2,2 KB.**
El art entero son 6.410 bytes minificados / 2.177 gzip. Y mi propia propuesta
para cerrarlo era falsa: quitar el fallback del canal no saca el art de ningún
bundle, porque `createActiveUix` y `defineEngineSound` lo importan
incondicionalmente. Cerrarlo de verdad exige carga diferida, que choca con que
`prime()` deba ser síncrono dentro del gesto, y volver `uix.sound` perezoso —
cambio de superficie pública por 2,2 KB. Se acepta: el art es servicio de
núcleo, como `motion` y `scene`. Queda corregida la doctrina: S-1 era el más
débil de los cuatro síntomas; los que justificaban la extracción eran S-2 y S-4.
**La voz, declarada como diseño.** La quinta a 1.5×, el ADSR recortado a
15 %/40 %, el contour de ±400 cents y el mapeo del AM son decisiones perceptivas
calibradas contra el vocabulario de sema, no maquinaria. Escrito en el README
del art y en la cabecera de `engine-sound.ts`, con la regla: el día que un
segundo consumidor quiera otra voz, ése es el momento de partir el art en dos
—gobierno de contexto / voz—, no antes.
Verificación: art + sema **17 suites / 204 tests** · `contracts.test.ts` 35/38
(los 3 rojos, ajenos) · `check` en la baseline exacta (73, 0 propios) ·
`docs:check` **0 errores** · los guards de A-5 **vistos fallar** al reintroducir
la escritura al master · navegador `/temas/sema`, 4 ciclos de editar→disparar:
**1 contexto, 16 osciladores, master en 1, cero errores de consola**.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
75017e5515 |
feat(blocks): `team` — las personas, y un contexto que sirve para una sola cosa
F2.12. Compound porque `Member` SE REPITE (el app mapea sobre N), como
`feature-grid`. Pero es el primero del tier cuyo **contexto existe para una sola
cosa**: el `align` de la sección viaja a cada `Member` y a su fila de enlaces, así
que la foto, el nombre, el cargo y los enlaces comparten eje sin que el app repita
la alineación en cada tarjeta. Un `align` explícito en una parte siempre gana.
Esa es la diferencia con `feature-grid`, cuyos ítems solo se repiten y por eso no
llevan contexto: **la regla de forma se aplica parte por parte, no block por
block**.
## Dos decisiones de forma
- **Sin `.MemberAvatar`.** El plan la dibujaba; no tendría nada que añadir sobre
`<Avatar size radius>` y escondería su API (`Image`, `Fallback`, los estados de
carga). El app compone el `Avatar` del canon directamente dentro del `Member`.
Se envuelve lo que el block DEFAULTEA —tipografía, retícula, eje—, no lo que
solo reenvía.
- **El nombre es un `Heading level={3}`** apagado con `size="sm"`: una persona en
una retícula tiene nombre, y las referencias lo marcan igual. El nivel es
estructura del documento; el tamaño, tipografía. Así un lector de pantalla puede
saltar de persona a persona.
Y un detalle que separa cumplir de sobresalir: la fila de enlaces va pegada al
fondo de la tarjeta (`marginTop: auto`). Con biografías de distinto largo las filas
quedaban a alturas distintas y la retícula se leía descuadrada; sin biografías las
tarjetas ya miden lo mismo y la regla no cambia nada.
## Encontrado al verificar
**Un `columns` fijo no colapsa.** A 420px, cuatro columnas dejan celdas de ~90px
con el nombre partido en tres líneas y el avatar desbordando. El default del block
es fluido (`minChildWidth`), así que el fallo era de la demo: ahora pasa
`columns={{ base: 2, md: N }}` — para eso están los props responsivos. Anotado en
el README.
Verificado en navegador, esperando el atributo que pone la runtime del morfo y no
el reloj: 2/3/4 columnas, `align` center/start propagándose por contexto al eje de
cada miembro, biografía sí/no, claro/oscuro/RTL y 420px, seis avatares, doce
botones de icono **con nombre propio por persona** («Escribir a Ada Lovelace», no
«correo»), escalonado estructural con índices 0,1,2…, cero desbordamiento
horizontal y cero errores de página. Gates: `blocks:check` verde (13 blocks) ·
`svelte-check` sin errores propios · `docs:check` 0 · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
c57b66dc92 |
feat(blocks): `banner` — el aviso de arriba, y tres defectos del canon que no existían
F2.11. El block más FINO del tier a propósito: cuando el canon ya tiene la pieza,
el block es colocación y nada más. Reenvía la superficie entera del `Banner` con
`Omit<BannerProps, 'children'>` —sin re-declarar `intent`/`variant`/`size` ni
estrecharlos—, lo mete en columna con `Container` (`width="100%"` +
`paddingX={0}`: a sangre por fuera, en columna por dentro), envuelve la fila con
`Wrap` en vez de aplastarla, y cablea `Banner.Close` solo si llega `onDismiss`.
La visibilidad NO es suya: es la decisión que ya tomó el componente
(«composición, no un booleano `dismissible`»), así que el app envuelve en su
`{#if}`. Un block que guardara ese estado sería una segunda fuente de verdad.
Tampoco emite sema: el canon declara cero eventos para `Banner` a propósito, y un
aviso persistente sin descarte es tan legítimo como uno descartable.
La demo lo enseña **encima del `site-header` de verdad**, con página para
desplazarse. Un aviso dentro de un recuadro no se parece a un aviso.
## El hallazgo que la fase 0 anticipó
`Banner` estampa `role="banner"` DESPUÉS de sus rest props, así que no se puede
relajar a una región normal. Con el `site-header` en la misma página —que es la
única colocación que shipean las referencias— quedan **dos landmarks `banner`**.
Medido en la vista previa: dos. Lo único que puede hacer un app hoy es nombrarlos
con `aria-label`, y eso hace la demo.
## Y una lección de método que costó una sesión
Reporté tres «defectos del canon» —`aria-label` ausente en `Banner.Close`, `type`
desaparecido de todo `Button`, `IconButton` tragándose el `onclick`— y **los tres
eran falsos**. La causa era la misma: medir demasiado pronto.
Los `data-variant` / `data-size` los escribe el componente de eidos al renderizar,
así que están desde el primer frame; `type`, `aria-label` y los handlers los
aplica la **runtime del morfo en un efecto posterior**. A ~1s: `type` ausente en
3 de 3 botones y ningún clic disparando. A ~6s: `type="button"`,
`aria-label="Descartar"` y el descarte funcionando.
La espera válida es un atributo que solo pueda haber puesto la runtime
—`waitForFunction(() => boton.hasAttribute('type'))`—, no que el nodo exista ni
que se vea. La regla queda afinada en `CONTINUE-blocks.md`, que ya avisaba de
esperar la condición y no el reloj: fallé al elegir la condición.
De paso, un aviso de soma que sí era real y era mío: `Tabs.List` sin nombre
accesible en `BlockDemo.svelte`. Afectaba a las 12 demos del tier.
Verificado en navegador: los 4 intents, los 3 tratamientos, `descartable` sí/no
(con `no` el botón deja de renderizarse, no se oculta), claro/oscuro/RTL y 420px,
la tira siempre por encima de la cabecera, cero desbordamiento horizontal, el
descarte con la página recolocándose, y los controles de la demo moviendo la
vista previa en línea. Gates: `blocks:check` verde (12 blocks) · `svelte-check`
sin errores propios · `docs:check` sin errores míos · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
cf37aee002 |
docs(sound): el aviso de segundo contexto dice lo que hace, no lo que promete
A-2 de la auditoría (`PLAN-sound-engine` §16). El contador de contextos vivos sólo ve **los que crea el art**, así que un `new AudioContext()` en cualquier otro sitio le es invisible: detecta cableado doble de ENGINES, no el antipatrón del contexto crudo que su propio mensaje describe. Ese hueco es exactamente lo que dejó al estudio de sema abrir un segundo contexto en silencio hasta ayer (A-1) — y lo que hizo inútil el plan del handoff anterior, que era «abre la página y mira el warn». Cerrarlo de verdad exigiría enganchar el constructor global, es decir parchear la plataforma. El art no lo va a hacer. Así que se escribe donde prometía de más, en `consts.ts` y en el README, y se apoya en la salida real: los consumidores que necesitaban su propio contexto ahora tienen puertas —`decode` para las muestras, `context` para el grafo— que no les obligan a abrirlo. Sin cambios de comportamiento. Art 13/13 · `check` en la baseline exacta (73). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
08ee18a8e1 |
fix(sound): `decode()` — la puerta que faltaba, y el estudio deja de abrir su contexto
A-1 de la auditoría (`PLAN-sound-engine` §16): `analyze.ts` hacía `new Ctor()` propio para decodificar el `.wav` importado. Medido inyectando un WAV real por el `input[type=file]`: **1 → 2 contextos**. Transitorio —se cerraba al acabar el decode— pero es exactamente el antipatrón que el art existe para impedir, en la misma página que usé como prueba de la invariante. Y el aviso de «segundo contexto» no lo vio, porque sólo cuenta los contextos que crea el art (A-2). No era un despiste de esa página: **la superficie del art no ofrecía la operación**. `preload(urls)` decodifica y esconde los `AudioBuffer` en una caché privada, así que quien quiere las MUESTRAS —un waveform leyendo picos, un analizador midiendo un fichero— no tenía puerta y se abría la suya. - **`EngineSound.decode(data): Promise<AudioBuffer | null>`**. Segunda puerta, simétrica de `context`: una para quien quiere un GRAFO, otra para quien quiere las MUESTRAS. Las dos llevan al mismo contexto. - **No pasa por `getOrCreateContext()`**: decodificar funciona sobre un contexto suspendido, y `resume()` fuera de un gesto puede quedarse pendiente para siempre — un `decode()` que se espera desde la UI no puede colgar de eso. Se añade `ensureContext()`, crear sin resumir. - **No cachea** (los bytes crudos no tienen clave; la caché por URL es de `preload`) y **devuelve `null` en vez de lanzar**. - **Desviación declarada** de lo que §16.2 proponía: NO se añade el acceso a los buffers cacheados. Hoy no tiene consumidor —`Waveform` no existe— y añadir superficie sin consumidor es justo lo que §16.1 le reprocha al art. - `analyze.ts` toma el motor por puerto estructural; `AnalyzePanel` lo lee de `getActiveUix()`, que es el idioma de la casa, no prop-drilling. MEDIDO en Chrome: importar un WAV crea ahora **1 contexto** en vez de 2, y el earcon posterior reutiliza ése. Y sigue analizando bien: un tono de 440 Hz a 8 kHz, decodificado sobre el contexto compartido a 44,1 kHz, se mide como **441 Hz** — el resampleo no falsea la medida. Guards nuevos en `engine-sound.test.ts`: decodifica sobre el contexto compartido SIN llamar a `resume()`, un segundo `decode` no crea otro contexto, y devuelve `null` con bytes indecodificables o sin contexto. Verificación: art + sema 17 suites / 201 tests · `check` en la baseline exacta (73 errores, 0 propios). Quedan abiertos A-2 (el aviso es ciego a los contextos crudos), A-3 (S-1 no está cerrado), A-4 (opciones del canal tiradas en silencio) y A-5 (`gainScale` en el master compartido) — los tres últimos cambian comportamiento público y esperan decisión. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
c0a7050f54 |
docs(process): registro de lectura — guías + theming leídos, e inventario corregido
Cuarta sesión de lectura del corpus. Leídos completos sobre el original:
component-guide (1568), component-audit (369), demo-authoring (266),
book-deviations (851) y todo theming salvo el changelog (gradient-finish,
guide, notes, channels). 4.247 líneas.
El hallazgo que más cambia el trabajo es sobre el propio registro: el
inventario estaba TRUNCADO. Contaba «30 documentos / 19.000 líneas»; el corpus
que el mapa declara son 59 ficheros / 27.348 líneas. Faltaban dos que el propio
corpus llama vinculantes — guides/demo-authoring.md (LOCKED, parte del «paquete
mínimo» de todo brief) y spec/delegation-contract.md (NORMATIVE, RFC-2119) — y
theming/changelog.md (1879), el mayor sin leer. Inventario ya corregido.
Segundo hallazgo de método: una lectura CADUCA. sema.md creció 1024 -> 1051 el
mismo día que se dio por leído (extracción del motor Web Audio al art $sound).
Comprobar wc -l contra el inventario al arrancar cada sesión.
15 hallazgos nuevos (nº 24-38), todos con doble anclaje: documento + línea sobre
el original, y comando contra el código. Los que más pesan:
- Sema tiene CUATRO canales en el código (AnnounceChannel incluido, cableado en
engine.ts desde
|
2 months ago |
|
|
b27f2345fe |
docs(process): el motor cierra sus fases y abre 5 hallazgos; el player, al gate
`PLAN-sound-engine` pasa a CERRADO (F0…F5) y `PLAN-audio-player` a DESBLOQUEADO. Pero cerrar las fases no es dejar el diseño incuestionado, así que el plan gana una auditoría propia que lo contradice donde toca. - **§15 — la invariante, cerrada y medida.** Incluye lo que la medición DESMINTIÓ: el handoff anterior predecía un aviso por consola en `/temas/sema` que no podía aparecer, porque el aviso cuenta contextos VIVOS y allí sólo había uno. Y **E-8**, el agujero de attach que E-1 dio por cableado sin estarlo. - **§16 — 5 hallazgos ABIERTOS sobre el art**, tres con decisión pendiente: `analyze.ts` sigue abriendo su contexto (medido 1 → 2 al importar un `.wav`); el aviso de segundo contexto es ciego a los contextos crudos; **S-1 NO está cerrado** y §7 afirmaba lo contrario —queda tachado en su sitio—; el canal tira en silencio `masterGain` y compañía cuando le inyectan el motor; y `gainScale`, que es de UNA reproducción, se escribe en el master compartido. - **§16.1 — la pregunta de fondo**: el corte separa módulos, **no conocimiento**. La quinta a 1.5×, el ADSR recortado a 15 %/40 %, el contour de ±400 cents y el mapeo de `roughness` son diseño sonoro calibrado contra el vocabulario de sema, viviendo dentro de lo que el plan llama «maquinaria». El precedente de `$motion` —datos en la capa, mecanismos en el art— no se replica tan de cerca como §3 afirma. Y la superficie no sirve a dos de los tres consumidores que la justificaban: `Waveform` y el analizador necesitan los `AudioBuffer`, y `preload(urls)` los esconde en una caché privada. - **`sema.md` corregido**: «sema no importa el art» era cierto para la INSTANCIA y falso para el grafo de módulos. - **`PLAN-audio-player` con las 5 correcciones de su cabecera incorporadas al cuerpo**, que era la condición para presentar nada: segmentos en la matriz §2 y en el contrato §7.5; **G-2 sube a bloqueante** (buffer, ventana de clip, capítulos); las dos invariantes de §7.6 (`prefs.sound` NUNCA toca el volumen del contenido; el silencio de UI debe alcanzar al `Slider` compuesto). **D-AP.11 escrita contra el art** —su premisa, «no hay motor», era falsa— y **D-AP.12** (segmentos `clip`) añadida; las dos existían como referencia sin fila en §4. **D-AP.7 reescrita** como ducking. - El handoff añade dos reglas que costaron caro: **un guard que no has visto fallar no vale**, y acoplarse al dev server vivo del usuario en vez de levantar otro. D-AP.1…D-AP.12 siguen SIN FIRMAR: el gate es lo primero de F0 y no se escribe una línea de código del player antes. `docs:check` deja 1 error ajeno (`blocks/banner/README.md:15`, «8 roles» por 9). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
dd7efae9cd |
docs(process): el handoff de blocks, reescrito para entrar en frío mañana
El fichero se había convertido en una pila cronológica: la cabecera seguía
contando el estado de hace tres blocks y los gates decían «8 blocks». Ahora es un
punto de entrada.
- **Estado honesto arriba**: 11 de **15** (el plan fijó 14; `feature-split` entró
como hermano con scope-approval, así que el denominador real es 15), qué queda
con su §, y dónde vive cada cosa (plan, contrato, dossier, deuda, READMEs).
- **F2.11 `banner` con fase 0 en pasos**: leer primero el README del componente
`Banner` que YA existe —si el descarte, la persistencia o el `role` son suyos,
el block los compone, no los reinventa—, contrastar ≥2 catálogos, decidir la
forma, y enseñarlo ENCIMA del header, que es donde vive.
- **La regla de forma del tier** en su propia sección, con los ejemplos ya
shipeados de cada lado (repiten / coordinan / slots).
- **Los 8 hallazgos de canon (F15–F22) en una tabla**, cada uno con su número
medido, en vez de repartidos por cinco párrafos. Más los dos huecos viejos que
siguen abiertos (`flex` que no crece, `Group` que no apila).
- **Tres reglas nuevas de las que costaron sangre**: esperar la CONDICIÓN y no un
timeout (con Form+Field+SIUM la hidratación tarda ~2,5s y un screenshot
temprano fotografía el panel invisible); Playwright headless SÍ vale para foco y
teclado reales, `page.fill()` no; y un slot que se apaga se pasa como
`undefined`, con el aviso de que `{#snippet signup()}` sombrea un prop del mismo
nombre.
- **Gates remedidos, no heredados**: `blocks:check` 11 blocks, `svelte-check` sin
errores propios, y los 3 fallos de `contracts.test.ts` verificados uno a uno
como ajenos (escrituras DOM en soma, data-attrs hardcodeados, claves camelCase
de `aura`) — ninguno apunta a `blocks/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
b7139cd825 |
docs(process): handoff en frío — el registro deja de exigir leerse entero
El registro había crecido a 450 líneas y una sesión nueva tenía que atravesarlas para saber dónde está y qué hacer. Ahora arranca con «Empieza aquí»: dónde está (21/30 documentos, ≈10.400 L), qué leer y en qué orden, las tres reglas que no se saltan, lo único que espera decisión, y el estado del árbol. Y se retira lo que ya se leía como TODO vivo estando cerrado: - La sección de contaminación queda marcada como histórica — se conserva porque la lección (una edición revertida citada como doctrina) vale más que el incidente, pero deja de pedir una relectura hecha. - Los tres «NO resolver antes de leer §25» de los hallazgos 7, 9 y 12 apuntan al nº 13, que los cierra. Un lector que llegue a ellos ya no cree abierta la pregunta del color. - El hallazgo nº 1 pierde su aviso de RELEER: el párrafo se confirmó sobre el original (`active-uix.md` 61–63). - El inventario dice explícitamente que no queda ningún documento en RELEER. Sin cambios en los hallazgos ni en el material leído. `docs:check` sigue con su único error preexistente (nº 21). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
6920684d09 |
refactor(sound): extraer el motor Web Audio de sema al art `$sound`
`sema/chans/sound.ts` tenía 407 líneas de las que ~255 (≈63%) eran maquinaria Web Audio —ciclo de vida del AudioContext, síntesis, samples, desbloqueo por gesto— mezclada con doctrina perceptiva. Deuda arrastrada, con tres síntomas medidos: el import era estático, así que toda app con sema metía el sintetizador en el bundle aunque el sonido estuviera apagado (que es el default); faltaba ciudadanía que `$scene` ya resuelve; y la costura de inyección (`audioContextFactory`) llevaba ahí sin usar desde el principio. Mismo movimiento que `$motion` hizo desde eidos: el art se lleva el RUNTIME, la capa conserva sus DATOS y su doctrina. - `$sound` / `EngineSound`: UN AudioContext por documento (los navegadores los limitan y el gesto de desbloqueo es por contexto), síntesis de earcon, samples con caché y fallback a síntesis, `autoSuspend` OPT-IN —suspender con la pestaña oculta es correcto para earcons y erróneo para contenido, así que es decisión de quien compone— y aviso cuando un segundo contexto va vivo. Puertos `SoundDom` / `SoundTimers` inyectados; no importa ningún otro art ni nada de `$uix/sema`, que es la prueba objetiva del corte. - `SoundChannel`: 407 → 136 líneas. Solo doctrina: el gate de `prepare`, la política de reducción y el reparto «el canal resuelve el NIVEL, el motor aplica la ganancia». Sema no gana ni un import: recibe el motor por puerto. - `uix.sound` en standalone y attach con `ownsSound` (idioma ya shipped: `ownsMotion` / `ownsScene`), fila `sound` en la tabla ejecutable `contracts.ts`, y `defineEngineSound()` para el camino de app. - Tests nuevos: `engine-sound.test.ts` (11), `sound-port.test.ts` (guard de deriva de tipos + la regla de propiedad) y `sound-e2e.test.ts`, que recorre `emit -> cascada -> canal -> art -> grafo real`: el camino que las 16 suites previas no cubrían porque paraban en canales falsos. Sin `diagnostics.ts` ni `errors.ts`, y es decisión: espejo de `$scene`, aquí todo fallo es degradación documentada, no error de programador. Verificación: 17 suites / 198 tests · `check` en la baseline exacta (73 errores, 0 propios) · `sound.test.ts` verde SIN tocar un solo assert, que era el criterio de que el movimiento fue value-preserving. Planes: `PLAN-sound-engine.md` (completo, con el registro de la revisión adversarial E-1..E-7), `PLAN-audio-player.md` (aparcado tras el análisis del reproductor, con sus correcciones en cabecera) y `CONTINUE-sound-engine.md` (handoff). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
37061a7371 |
docs(process): canon + theming completos — el corpus ya tenía escrita la respuesta
Tercera sesión de lectura: el canon entero (tsc · vocabularies · recipe-contract), lo que faltaba del núcleo (active-app · agent · blocks) y `theming/reference.md` COMPLETA. 21 de 30 documentos leídos sobre el original (≈10.400 L de 19.000). ## La pregunta del color: el corpus la tenía respondida, dos veces y mejor `reference.md` §1.bis se titula «Theming lives in Eidos, not in Morfo (by design)» y se abre con «This is the most frequent architectural question». Existe literalmente para impedir el error que cometió la sesión revertida. Su apartado «What SHOULD enter morfo regarding theming» es inequívoco: un componente que expone `color` como prop DEBE declarar `data-color.values` en su morfo; los attrs puramente visuales (`data-variant`, `data-size`) NO. Y §39 (2026-07-15, la doctrina más reciente) da el criterio mecánico y el porqué: «morfo declares an attr only when its driving prop crosses the soma boundary», con el modo de fallo medido — si declaras en morfo un attr cuyo prop no llega a soma, el runtime emite `undefined` y `mergeProps` pisa el sello del wrapper. La frontera deja de ser convención: es de dónde se resuelve el valor. ## Y el agujero real, ahora seguido extremo a extremo `resolveComponentColor` (`eidos/lib/component-color.ts`) considera canónicos los 9 roles MÁS las 33 escalas donantes = 42 nombres; sólo un color CSS crudo se desvía a `data-color-custom`. El wrapper de switch pasa ese `dataColor` a soma, y el provider lo devuelve verbatim. El morfo declara 6 valores. `<Switch color="teal">` emite `data-color="teal"`, fuera del enum — y no es un caso aislado: avatar 8 · button 8 · card 8 · switch 6 · toggle 6 · dialog 3, contra 42 posibles. NO verificado si salta hoy en CI: `morfo:check` sólo ve lo que las demos renderizan de hecho, y no lo he ejecutado. Ese es justamente el agujero — el guard de «lo declarado se cumple» depende de que una demo elija el valor infractor, en el eje que la decisión de 2026-07-18 abrió a 42. Es el único hallazgo que pide decisión de usuario. ## Dos derivas más - `active-app.md` enumera a mano 12 service factories; hay 15 (faltan `agent`, `motion`, `scene`, en la tabla y en el árbol). Misma enfermedad que el «8 roles» y contra la ley que el propio corpus escribió: catálogo hardcoded → puntero. - `canon/tsc.md` anuncia el scope `event:` sin nota, mientras `reference.md` §13 lo marca «Superseded» y `motion.md` «descartado». El tipo existe, cero recetas lo consumen: las tres frases son ciertas, pero quien construya desde el canon lo leerá como disponible. Nada arreglado (regla 5). Quedan ≈6.200 líneas: las guías, `decisions/` y los RFCs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d8ff647d7c |
docs(process): los 12 contaminados, releídos — y la pregunta del color, cerrada
Segunda sesión de lectura. Los 12 documentos que el revert dejó marcados
`RELEER` (≈5.000 L) leídos enteros sobre el original, más `theming/reference`
§20–§28, que era donde el propio registro decía que estaba la mitad que
faltaba.
Paso 0, antes de leer nada: comprobar que el árbol no sigue contaminado.
`git diff c39170abb^ HEAD` sobre los docs doctrinales devuelve vacío — HEAD es
byte a byte el estado pre-auditoría. Cuesta un comando y es lo que separa la
doctrina de la autocita; queda escrito como regla 7.
## La pregunta aparcada: `data-color` SÍ va en el morfo
Tres anclajes independientes, ninguno citándose a sí mismo: la tabla §6 de
`active-architecture` lo pone en la casilla de `dom.apply (effect)` — la misma
que `data-state`, no la del wrapper — y `git log -L` traza esa fila a
`d4dfdaffa`, muy anterior a cualquier agente; las reglas E-3.5 / R-3.1 / R-3.3
/ G-1.2 del checklist; y 12 morfos que lo declaran con `v.propRef('color')`.
La conclusión de la sesión revertida era falsa.
Debajo había una pregunta mejor, y esa sigue abierta: el morfo de `switch`
declara `data-color` con 6 valores cerrados, mientras la decisión de 2026-07-18
(§25) manda que `color` acepte el sistema completo — 33 escalas y valor CSS
crudo — con el tipo abierto `ComponentColorProp`. Y `morfo-check.ts:167` falla
cuando un attr con `values` emite algo fuera del conjunto. NO verificado si hoy
salta: pide una demo con valor fuera del enum y `morfo:check` con dev server.
Es lo único de los nueve hallazgos que pide decisión de usuario.
## Lo demás, verificado contra el código, que es lo que no puede haber escrito un agente
- `provider.commitState()` / `emitEvent()`: cero coincidencias en todo el repo.
Los documentan dos docs. La sesión revertida acertaba en el hecho y falló en
el método; entre sí los dos docs no se contradicen — la deriva es doc↔código.
- El guard de tests de provider no existe: `NO_MISSING_PROVIDER_TESTS` sólo
aparece en docs, sin comprobación en `scripts/` ni invariante en
`contracts.test.ts`. Cierra por la puerta buena la pregunta que quedó abierta.
- `clsx` (soma-architecture stale contra soma.md y contra el código), Aura y
`uix.scene` (packs y glossary describen un futuro que ya ocurrió), el
frontmatter de CANON apuntando a un PDF que no está, el entry huérfano de
`package.json`.
- `docs:check` lleva en rojo desde el revert y por el revert: `c39170abb`
arreglaba `callout/README.md:23` y `3097cfcb6` lo deshizo con todo lo demás.
El revert deshizo también arreglos buenos; su diff merece una pasada.
Nada arreglado — la regla 5 lo prohíbe hasta acabar, y quedan ≈11.900 líneas.
Lo no hecho queda enumerado en el propio registro.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
eb13fff409 |
feat(blocks): `site-footer` — el pie de la página, con el selector de idioma vivo
F2.10. Es el block donde la regla de forma del tier se ve mejor: `Column` SE
REPITE (el app mapea sobre N grupos), así que es parte compound; la marca, la
banda de alta, lo social, lo legal y el `extra` no se repiten ni coordinan, así
que son slots. El landmark es un `<footer>` de verdad: `contentinfo` sin pedir
nada.
Las columnas van en `AutoGrid minChildWidth`, **sin un solo breakpoint**: el mismo
código sirve para 2 grupos o para 5, y en móvil caen a dos. La entrada es un
`Motion trigger="viewport"` solo en la región superior y **sin escalonado** — una
línea de copyright que aparece con fundido es teatro, y nadie lee un pie columna
por columna.
## Los dos huecos que nombraba el dossier, cerrados
- **La banda de alta al boletín EN el pie** (3 de 7 en Tailwind Plus) como slot
`signup`, con su propia fila a lo ancho: en la columna de la marca (~230px) la
fila de correo tendría que apilarse. El formulario es del app; este block NO
importa el block `newsletter` (B-10) ni inventa validación.
- **El selector de idioma** como `extra` (el slot libre de D-BLK.6), y está VIVO:
escribe `uix.prefs.setIntent('language', …)`, el eje real del ecosistema.
Verificado — al elegir «English», `document.documentElement.lang` pasa a `en`.
## Tres números que salieron de medir, no de suponer
- El `container` bajó de `xl` a **`lg`**: con `xl` el pie no se alineaba con
ninguna sección de la página compuesta encima.
- La rejilla superior pasó de `1fr 2fr` a **`1fr 3fr`**: con `2fr`, un pie de
cuatro grupos se partía en 3+1.
- El gap entre columnas es **6, no 8**: con 8, cinco grupos no comparten fila al
ancho `lg` (728px justos). Horizontal más apretado que vertical, porque el gap
horizontal es el que decide cuántos grupos caben.
## Dos hallazgos de canon (F21/F22 en `PLAN-blocks-quality.md` §6)
- **Los primitivos de layout no pueden cambiar de elemento.** `Text` y `Heading`
aceptan `as`; `Box` —y por tanto `Stack`, `Flex`, `Grid`, `Group`, `Wrap`,
`Container`, `Section`— renderiza un `<div>` fijo. Consecuencia: una columna de
enlaces no puede ser `<ul>/<li>`, que es como la marcan las referencias. Un
block solo puede elegir entre divs o escribir markup que luego no puede estilar.
- **Un `Select` controlado muestra el VALOR crudo hasta que se abre una vez.**
`getDisplayText()` resuelve contra un registro de etiquetas que llenan los
`Select.Item` al montarse, y con el `Content` en un portal cerrado no hay ninguno
montado: `value=['es']` pintaba «es» en vez de «Español». Rodeado con el `child`
de `Select.Value`.
Verificado en navegador: 2/3/4/5 columnas, banda de alta sí/no, claro/oscuro/RTL
y 420px (2×2 columnas, fila de correo apilada), cero desbordamiento horizontal,
separador `aria-hidden`, los tres `IconButton` con nombre obligatorio, y el
idioma cambiando de verdad. Cero errores de página. Gates: `blocks:check` verde
(11 blocks) · `svelte-check` sin errores propios · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
00118dd9e4 |
docs(process): eidos.md completa — 'eidos-only' es clasificacion valida
`architecture/eidos.md` (1055 lineas) leida completa, limpia. Dos piezas que casi cierran la pregunta aparcada sobre `data-color`: - §Selector-drift defense define **eidos-only** como «the marker is there, but at least one `data-*` is not declared in the morfo. **Valid by convention** (visual tokens like `data-variant`, `data-size` come from the wrapper)». Solo *invalid* es bug. - THM-4 da la disposicion inversa: un attr «declared FOR a visual axis and that nothing anywhere consumes» es deuda, y se «consume it or prune it from the morfo». Juntas apuntan a que un `data-color` del wrapper ausente del morfo es legitimo, y que uno declarado solo por eje visual es podable. Pero falta `theming/reference.md` §25, que `eidos.md` designa canon del color: la pregunta NO se cierra hasta leerla. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
5f01746426 |
docs(process): eidos.md hasta la 750 — media respuesta a la pregunta aparcada
`eidos.md` leida hasta la linea 750 de 1055 (limpia, no la edito la sesion revertida). Lo relevante: existe una categoria SANCIONADA de data-attrs visuales que estampa el WRAPPER y que no van al morfo — linea 489: «The wrapper adds the visual token data-attrs (`data-variant`, `data-size`, `data-block`, `data-icon-only`)». Y la tabla «From morfo» enumera lo que eidos consume del contrato: `data-color` NO aparece. Eso reconcilia a medias la regla de `morfo.md` 878 («lo que el provider emite, se declara») con la pregunta sobre `data-color`: la regla habla del provider de soma; hay otra via legitima por el wrapper. `data-size` es el precedente verificable (`switch.css` lo usa y no esta en el morfo). Falta la otra mitad — si `data-color` pertenece a esa categoria. La lista de la 489 es ilustrativa y no lo incluye; `eidos.md` remite el canon del color a `theming/reference.md` §25. Anotado como NO resolver antes de leer §25. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
f69cf76cfd |
feat(blocks): `newsletter` — el alta al boletín, y el reset que el framework daba por hecho
F2.9. Slots de snippet (eyebrow · title · description · field · submit · note ·
children), `center` y `justified`, y `panel` como interruptor real: el panel de
marca es la cara de las referencias, pero el control, la ayuda y el error están
calibrados para la superficie de página, así que apagarlo es un estado de primera
y no un fallback.
La fila es `Grid templateColumns={{ base: '1fr', sm: '1fr auto' }}`: el campo se
queda la pista libre y la acción abraza su contenido —eso es lo que hace que un
alta se lea como UN gesto— y bajo `sm` pasa a una columna, porque un botón al lado
de un campo de correo deja inservibles a los dos.
El block NO valida y NO emite sema. `Form` posee el runtime, el esquema, dirty/
touched, la agregación de errores y el foco al primer error; `Field` posee el
cableado ARIA; el morfo del `Form` ya declara `commit-submit` y `signal-invalid`.
El app posee esquema, valores y handler. No hay ni un `if` sobre un correo aquí.
## Superación del dossier: la etiqueta
El único hueco que el dossier nombraba era la nota de privacidad, y está. Pero la
diferencia de verdad es la etiqueta: las referencias shipean la fila escondiéndola
con `sr-only` o dejando solo un placeholder. El canon tiene `Field floatingLabel`
—arranca dentro del control y sube al borde al enfocar o rellenar—, así que la fila
queda alineada CON etiqueta real y asociada. Verificado con pulsaciones de teclado
de verdad: 10px dentro en reposo → −11px sobre el borde al enfocar y al rellenar.
## Tres hallazgos de canon más, medidos
- **La fundación de eidos no trae reset de modelo de caja y lo asume del app.**
`[data-field-control]` declara `inline-size: 100%` + padding, así que bajo
`content-box` el control mide 30px más que su contenedor: el campo se metía por
debajo del botón de envío. Campo 480 / control 510 en la galería frente a
502 / 502 en los docs de componentes, que sí resetean (igual que
`web/routes/active/styles.css`). Arreglado en app-land con
`web/routes/blocks/_lib/reset.css`, con A/B sobre los 10 previews y 5 páginas de
shell: cambia el newsletter y NADA más. Hay que importarlo dos veces porque la
galería arranca UIX en línea en vez de pasar por `BootUix` — deuda del arnés,
anotada en el handoff.
- **`onValidSubmit` es un no-op silencioso** cuando se pasa un `form` ya
construido: el componente solo lo reenvía al `createForm` que hace él mismo. El
envío validaba, limpiaba el error y no anunciaba nada. Por eso el block no
expone el prop: el handler va en el `createForm` del app.
- **Los mensajes de SIUM son idlangref.** La vía correcta es
`uix.langs.t(issue.message, issue.params)` —verificado, sale «Debe ser una
dirección de correo válida»—, pero la demo de docs del propio `Form` parte la
cadena a mano tras el `|`, así que el único ejemplo del repo enseña el patrón
equivocado y siempre muestra inglés.
Los tres quedan en los gaps del README y en `PLAN-blocks-quality.md` §6
(F18/F19/F20), sin tocar nada fuera del tier.
Verificado en navegador el arco completo: correo inválido → error traducido con
`role="alert"`, `aria-invalid`, `aria-describedby` y foco al primer error; correo
válido → confirmación del app (`Callout` afirmativo con la dirección) y error
limpio. Claro/oscuro/RTL, tres colores, `panel` sí/no, 420px. Cero errores de
página. Gates: `blocks:check` verde (10 blocks) · `svelte-check` sin errores
propios · prettier limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2 months ago |
|
|
25f8126e0a |
docs(process): morfo.md leída — y una regla que cuestiona la conclusión revertida
`architecture/morfo.md` (888 lineas) leída completa en su estado original. Era la prioridad del registro: la capa del contrato, la mas tocada en la auditoria fallida, y en la que se escribio criterio sin haberla leido. Lo que sale: - El hueco documental de §Step 2 (omite `'private'`, la lista de referencia si lo documenta) es REAL, confirmado contra el original. Lo que estaba mal era el criterio que se escribio ahi como arreglo, que clasificaba mal a los primitivos de API plana. El hueco sigue abierto. - `emit: 'value'` es forma legitima para attrs que llevan valor real. - Y una regla que CUESTIONA la conclusion revertida sobre `data-color`: «Provider emits a data-attr not in the morfo → morfo-check fails in CI. Either add the attr to the morfo or rename it to `data-_*`», mas el incidente 2026-05-20. La sesion revertida concluyo que `data-color` no debe ir en el morfo por ser vocabulario de eidos; esta regla dice que lo emitido se declara. Queda anotado como NO resolver hasta leer `eidos.md` completo y `theming/reference`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
33d4e81daf |
docs(process): registro de lectura del corpus doctrinal
La lectura del corpus no cabe en una ventana de contexto (~19.000 lineas). Sin registro, cada sesion la reempieza o se la salta — que es como se auditó a ciegas el 2026-07-29/30 y hubo que revertir 7 commits (`3097cfcb6`). Lo importante del fichero no es el inventario, es el aviso de contaminación: la lectura del 2026-07-30 se hizo sobre el árbol CON las ediciones de `c39170abb` dentro, así que 7 de los 10 documentos leídos devolvían la escritura del propio agente como si fuera doctrina. Dos casos medidos: - `testing-and-tooling.md` — el original dice que el guard de tests por provider existe; la edición lo había cambiado a «convention, not a guard», y se citó esa frase para justificar una decisión de arnés. Autocita. - `overview.md` — la edición sustituyó «Three Soma scenarios» (`commitState`/`emitEvent`) por «One door: runtime.trigger», y con eso se «descubrió» una contradicción contra `soma-architecture.md` que la propia edición había creado. Quedan por tanto 3 documentos de lectura válida (`README`, `sema`, `eidos` parcial) y 7 marcados RELEER. `morfo.md` (888 líneas) sigue sin empezar, y es la prioridad: es la capa del contrato y se llegó a escribir criterio en su §Step 2 sin haberla leído. Se registran los 4 hallazgos que sí se sostienen (todos de documentos limpios y verificados como originales) y se retiran los 2 que eran autocita. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
03d6796582 |
feat(blocks): `cta` — el panel que pide el siguiente paso, y tres mentiras que destapó al medirlo
F2.8. Slots de snippet (eyebrow · title · description · actions · children),
misma forma que `hero`: las partes de un CTA no repiten ni coordinan, así que un
compound no se gana nada. Dos disposiciones: `center` para cerrar una página y
`justified` —copia al inicio, acciones al final— que es el hueco que nombraba el
dossier. Un solo `Motion trigger="viewport"`: el panel llega ENTERO, porque un
CTA es una sola afirmación y repartir sus tres partes se leería como duda.
Lo que salió al verificarlo en navegador, medido y no a ojo:
- `variant='soft'` **fuera de la API**. Su track queda a `oklch(0.9932)` contra
un `--color-surface-default` de `oklch(0.9911)`: 0.002 de luminancia, o sea
ningún panel en claro. Y `Surface` no tiene borde al que caer. Cortar el prop
es más barato que shipear un estado que se esfuma.
- La ranura `contrast` de la paleta es blanco en TODO escalón sólido, así que un
lienzo de luminancia media deja el cuerpo por debajo de AA: `primary` 5.18 ·
`indigo` 5.21 · `plum` 4.75 pasan en ambos modos; `neutral` 3.32 · `teal` 3.07
fallan en claro. El block reenvía cualquier `color`; la demo solo ofrece los
que pasan.
- `Text align` es inerte por defecto: renderiza un `span`, y `text-align` no hace
nada sobre una caja inline. `align="center"` dejaba la copia a la izquierda
dentro del layout centrado, sin avisar. Rodeado con `as="p"`.
Y `Group` no apila: a 420px la etiqueta de la acción secundaria se parte contra
el botón primario, así que las acciones van en `Flex direction={{ base:
'column', sm: 'row' }}`. `hero` compone las suyas con `Group` — anotado.
Los tres hallazgos de canon quedan en los gaps del README del block y en
`PLAN-blocks-quality.md` §6 (F15/F16/F17), sin tocar nada fuera del tier.
Verificado: `center` y `justified` en claro/oscuro/RTL y a 420px, tres colores,
entrada disparada, descripción en `<p>` centrada, cero errores de página.
Gates: `blocks:check` verde (9 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
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 |
|
|
c9b83d0257 |
docs(morfo): qué hace pública a una parte, y tres hallazgos retirados
`morfo.md` §Step 2 — la guía de AUTORÍA, donde el autor decide — documentaba sólo `'public'` y `'virtual'` y omitía `'private'`. La sección de referencia sí tenía los tres, pero nadie declara partes leyendo la referencia. El criterio que queda escrito: **lo que hace pública a una parte es que el consumidor la decida, con independencia de la sintaxis**. La composición (`<Dialog.Trigger>`) es la vía habitual, no la única: un primitivo de API plana gobierna sus partes por props y snippets — `<Badge dot>` decide la parte Dot, `icon` la parte Icon. Ambas son públicas. `'private'` queda para lo que nada de lo que escribe el consumidor alcanza (el Track de `Spinner`, el Sentinel de `Sticky`). Importa el matiz porque la primera versión de esta nota decía lo contrario — que el test era mecánico y que sin `<Componente.Parte>` la parte no es pública — y eso habría reclasificado mal a Badge y a todo primitivo de API plana. ## Handoff: tres hallazgos retirados Los tres salieron de heurísticas de una sola vía dadas por hechas sin abrir el código. Es el mismo fallo que la auditoría denuncia en los guards, cometido al redactar el informe: - **D-3 `AgentTimersPort.once`** — el puerto SÍ está satisfecho: `defineActiveAgent()` adapta `core.timers.schedule` a `once` en la raíz de composición, que es el patrón previsto. No había nada roto. - **«5 derivas de scope»** — `avatar-group`, `path-trace` y `rotate-align` están implementados; el framework co-loca sub-componentes dentro del directorio del padre y la heurística asumía «directorio == kebab». - **«29 morfos con `kind:'public'` irreal»** — medía si existe `<Componente.Parte>` e ignoraba props y snippets. Sin recuento fiable hasta cruzar las tres vías. Añadido: `npm run lint` nunca ha pasado. Separando el ruido CRLF del checkout Windows (`--end-of-line auto`) quedan 1544 ficheros con deriva real de contenido. Es decisión del usuario porque el arreglo toca todo el repo de golpe. Y la lección de método: **un analizador que no puede resolver algo tiene que decir «no verificado», no emitir un hallazgo.** Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
4e785d32bf |
feat(blocks): F2.7 `stats-band` — la cifra que se cuenta sola
La fila de números que respalda lo que la página acaba de afirmar. Compound
(`.Stat` repite, sin contexto): `<StatsBand>` + `.Stat` + `.Value` + `.Label`.
- **Compone, no reinventa**: la semántica de KPI es el `Metrics` del canon, leído
de su API y pasado tal cual (precedente `faq`/`Accordion`). `Metrics` es
surfaceless, que es exactamente lo que una banda necesita: no son tarjetas de
panel. Un delta, un icono o un sparkline son `Metrics.Delta`/`.Icon`/`.Chart`
que la app compone dentro — el block no los re-expone.
- **La superación literal del dossier**: `<StatsBand.Value count={12500} />`
compone `CountUp`, así que la cifra se cuenta sola al entrar en pantalla.
NINGUNA referencia puede shipearlo: todas entregan markup estático. El formato
locale-aware y el salto directo bajo reduced-motion vienen del `CountUp`, no
de aquí.
- **`count` es opt-in, nunca default**: una cifra que se anima sin que el lector
lo pida es ruido, y algunas no son contables («99,98 %»). Sin `count`, la
cifra la pone la app por children.
- **Ni una palabra ni un separador salen del block** (B-7): las palabras por
children, el formato por `uix.format` dentro del `CountUp`.
- Escalonado estructural: `data-stagger` en la banda y cada `.Stat` es un
`Motion trigger="viewport"`, así que las cifras aterrizan una tras otra sin un
solo milisegundo escrito a mano.
Verificado en navegador (con la banda bajo el pliegue, para cazar el conteo desde
el primer fotograma): 4 cifras con índices estructurales 0,1,2,3 contando
—9377→11.678 · 255→318 · 36→45—, el porcentaje quieto por no ser contable, y el
separador de millares por locale (`11.678`). Cero errores de página.
`blocks:check` verde (8 blocks) · `svelte-check` sin errores propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
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 |
|
|
cdc00bc117 |
fix(blocks): lo que la auditoría encontró DENTRO del tier
Una auditoría multi-agente (7 dimensiones doctrinales + refutación adversarial) confirmó 32 hallazgos sobre la pasada de calidad. Esto corrige los que caen dentro de `blocks/`; la deuda de fuera queda REGISTRADA, no tocada (`PLAN-blocks-quality.md` §6). - **B-7 roto en `pricing`**: el block traía tres strings castellanas propias como defaults del toggle — dos visibles y un nombre accesible. Hardcodeaba un idioma DENTRO del framework y esquivaba `langs`, en el mismo block cuyo README predica «cero strings propias». Ahora los tres son props REQUERIDAS y las pone la app. - **El block pintaba**: `pricing.Plan` metía `box-shadow` por `style=` porque `Card` no expone elevación. Retirado; el hueco queda registrado como candidato a canon en vez de falseado. - **El escalonado estaba roto y el comentario mentía**: puse `spring-pop` (driver JS) en los planes, y un driver JS no lee el `animation-delay` donde vive el stagger — entraban TODOS a la vez mientras el comentario afirmaba lo contrario. Vuelto al preset CSS: verificado, índices 0,1,2 a 70ms. - **Comentario falso en la demo del hero**: atribuía al `intent` visual una consecuencia sonora/háptica que ningún camino produce (el morfo del Button dice explícitamente que `contact-activate` NO lleva intent). Reescrito. - **Composition maps mentían**: los 6 blocks que ahora componen `Motion` no lo decían; el hero seguía diciendo `Heading` (es `Display`), sin `Backdrop` ni `decor`, y declaraba DIFERIDO un preset de cromo de media que ya está shipeado (`Mockup`). Al día, sin filas duplicadas ni datos rancios (el chip de feature-grid es `solid`, no `soft`). - **Handoff y plan**: el plan seguía prescribiendo un `<Reveal>` que esta misma sesión creó y retiró; el handoff daba `feature-split` por «sin decidir» estando hecho, y los gates con números falsos. Verificado: `blocks:check` verde (7) · `svelte-check` sin errores propios · en el navegador, pricing escalona de nuevo, las etiquetas vienen de la app y ninguna tarjeta pinta sombra. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
a25be8903b |
docs(agent): el handoff deja de ser un diario y pasa a ser un punto de arranque
Se había convertido en un log por sesiones — la forma equivocada para abrir en frío, donde lo que hace falta es «qué hago ahora», no «qué pasó». Reordenado por lo que necesita quien llega: 1. lo PRIMERO al abrir (tres comandos; si no están verdes, eso es el trabajo) 2. lo BLOQUEADO — las dos firmas de §D, con el porqué de que §D.2 bloquee F4b (la escalada lleva razón pero no pregunta; la resolución no lleva respuesta) 3. lo DESBLOQUEADO — los dos manifiestos de F4b, que no dependen de §D 4. estado, flecos y lo que está sin firmar pero no bloquea 5. verificación 6. disciplina y gotchas, agrupados (git · verificación visual · trampas de código que ya mordieron) 7. la historia, comprimida al final Los gotchas ganan tres entradas nuevas que costaron tiempo esta sesión: el HEAD puede retroceder bajo tus pies en una rama compartida (comprobar con merge-base antes de alarmarse), `createEngineTimers()` como `timers` del motor tipa pero revienta, y `asserts condition` no se puede desestructurar. Sin cambios de código. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
8b89c7f6b7 |
feat(agent): el kit de conformance del contrato de delegación — portable de verdad
El segundo vehículo que la spec exige (§0.5 · Annex C.1), y el que de verdad acredita: `agent-check` es un lint de NUESTRO repo; esto lo corre un tercero contra SU implementación. PORTABLE POR CONSTRUCCIÓN El kit no importa `createEngineAgent`. Habla con un `ConformanceSubject` estructural —start · run · authorize · reject · resolveEscalation · stop · disable— que el implementor adapta sobre su motor; `subject.ts` es el ejemplo trabajado de ese adaptador, no una dependencia. Vive fuera del barrel (precedente `adapters/*`, D-AG.1c): quien no corre conformance no lo paga. DIEZ CASOS, CADA FALLO CITANDO SU ID AG-2 · AG-4 (el control vuelve aunque el transporte reviente — la lectura más dura del requisito) · AG-5 · AG-6 (escalada no terminal + razón tipada) · AG-9 (interrumpir es volver, no un error) · AG-10 (kill global) · AG-21 (una capacidad inventada vuelve como resultado corregible, no mata el run) · AG-25 (lo irreversible no se ejecuta sin revisar ni con el techo más suelto) · AG-26 (clamp de iniciativa) · AG-50 (degradación sin transporte). Un caso que falla solo se falla A SÍ MISMO: una implementación a la que le falte un requisito recibe igualmente el informe completo del resto, que es la diferencia entre una suite de conformance y un smoke test. Y EL KIT SABE FALLAR Un kit que no puede fallar no prueba nada, así que hay un test que rompe AG-4 a propósito y comprueba que el informe lo nombra. No es lo mismo que `engine-agent.test.ts`: aquella suite prueba el MOTOR con acceso a sus interioridades; ésta prueba el CONTRATO por la misma puerta que tiene un tercero, y es la que nos pillaría enviando un eje que nuestra propia especificación rechaza. AG-35 y AG-41 quedan FUERA a propósito mientras §D de la spec siga abierta — un kit que afirmara un requisito sin decidir estaría inventando la decisión. Hay un test que fija esa ausencia. TRAMPA CAZADA, QUE ES LO QUE HACEN LAS SUITES Tres casos fallaban por lo mismo, y no era el motor: pasar `createEngineTimers()` como `timers` TIPA pero revienta — `EngineTimers` expone `schedule`, no `once`, así que el primer timeout llama a `undefined` y el run cierra como `transport-error`. La factory de `active-app` adapta el puerto justamente por eso. Anotado en el handoff. Verificado: agent 33/33 (10/10 conformance) · arts:check 24/24 · agent:check limpio · docs:check sin errores nuevos · check de vuelta en los 73 preexistentes (los 17 que aparecieron eran míos: `asserts condition` exige llamarse por un nombre anotado, y yo lo desestructuro del contexto). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
31580f3b39 |
docs(spec): el contrato de delegación como especificación normativa (DRAFT)
Fase 1 del plan de corrección, y materialización de D-AG.12a firmada ayer: `docs/spec/delegation-contract.md`, versionada por fecha (2026-07-28). QUÉ ES 50 requisitos `AG-1…50` en RFC-2119, con escalera de estabilidad por sección (stable | provisional | reserved), identificadores estables que NUNCA se renumeran ni se reciclan (un requisito retirado conserva su número), y la exigencia de que ambos vehículos de conformance CITEN el identificador que incumplen — «una suite que no nombra lo que falla no acredita a nadie». La doctrina pasa a ser el PORQUÉ y lo declara explícitamente: si doctrina y spec discrepan sobre un requisito, manda la spec y la doctrina se corrige. Annex A lleva las dos reclamaciones de frontera con estado `claimed` (ninguna materializada) y su gate de promoción: ≥2 FORMAS DE DOMINIO con manifiesto conforme — y dice por qué dos participantes de texto no valen: un formato probado solo sobre texto secuencial asume texto secuencial en silencio. Annex B mapea el modelo de amenazas a requisitos concretos en vez de dejarlo como prosa. §0.6 declara la posición honesta (actos secuenciales, cliente- first, generalidad en prueba). LO QUE LA ESCRITURA SACÓ A LA LUZ — y no estaba en ninguna lista - **AG-35 (presencia)**: el invariante correcto NO es «Aura montada» sino «alguna superficie está expresando el ciclo». El playground de agnt ya expresa el ciclo SIN Aura, y exigir el componente lo rechazaría siendo correcto. Además resuelve el problema técnico: el motor es un arte sin DOM y no puede saber qué hay montado, pero sí puede contar adjuntos. - **AG-41 (elicitación)**: especificado y NO cumplido, con el porqué estructural — la escalada lleva razón tipada pero no la pregunta, y la resolución es binaria y no lleva respuesta. No era falta de superficie. - **AG-44**: «ningún estado se distingue solo por el color» sube de decisión de componente a requisito del contrato (ya lo fija `shapes.test.ts`). §D — DECISIONES ABIERTAS, VISIBLES Un DRAFT puede llevarlas; al promocionar, §D debe quedar VACÍA. Son dos y ninguna la firmo yo: `SHOULD` vs `MUST` en AG-35 (con recomendación razonada: SHOULD + política que lo eleve a MUST, porque rehusar por defecto rompería los 30 tests del motor y le quitaría al app el derecho a elegir su superficie) y la forma del canal de elicitación en AG-41. Registrada en el índice del corpus. `docs/README.md` lo edita otra sesión en paralelo: stageada SOLO mi fila (backup → HEAD → mi cambio → add → restaurar su versión + mi cambio), verificado que lo suyo queda sin stagear. Verificado: docs:check 542 docs, sin errores nuevos (el único es el ajeno preexistente de callout). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
f190a2266e |
feat(eidos): `Reveal` + `Backdrop` + cascada por viewport — la capa de acabado
Auditoría del tier (docs/process/PLAN-blocks-quality.md): los 7 blocks de F2 componían EXCLUSIVAMENTE la capa de layout (Box/Stack/Grid/Section/Text/Card). Uso de la capa expresiva del framework: cero. Ni un Motion, ni un Cascade, ni un Display, ni un TextGradient, ni un CountUp. La causa es estructural, no de gusto: el contrato B prohíbe que un block traiga `.css`, así que **un block solo puede ser tan expresivo como los componentes del canon que compone** — y faltaban dos primitivas. El arreglo no es maquillar 14 blocks uno a uno, es construir lo que falta en el canon y componerlo. - **`Reveal`** — reproduce la ENTRADA de un preset al entrar en viewport. No posee keyframes ni timing: estampa `data-animation-style` + `data-state='open'` cuando dispara el observador, así que reduced-motion degrada DENTRO del preset. Mientras espera, el recipe lo mantiene invisible bajo `@media (scripting: enabled)`: sin JS la regla no aplica y el contenido se ve siempre — el fallo de una animación de scroll debe ser «sin animación», nunca «página en blanco». - **Escalonado gratis**: el foundation ya escribe `--motion-stagger-index` en cada hijo de `[data-stagger]` (por `:nth-child`, sin JS) y los presets retrasan por `índice × --motion-stagger-each`. El recipe de Reveal le da ritmo por defecto, así que una lista se escalona poniendo `data-stagger` en SU contenedor: cero delays a mano, cero números en el consumidor. - **`Cascade trigger="viewport"`** — la cascada existente esperaba a `open` (disclosure); ahora puede esperar también a ser vista. - **`Backdrop`** — la capa de profundidad que no existía: `glow` · `mesh` · `grid` · `dots`, pintada en un `::before` desde tokens (acento por el forward de paleta, mesh desde `--gradient-aurora`), con `fade` para no cortar en seco. **El hero como prueba del nuevo listón**: `Display` (el primitivo de tipografía de hero que existía y no usaba — el titular pasa de `Heading` a 80px reales), entrada escalonada de eyebrow/título/subtítulo/acciones, media con `scale-fade` y `decor` (glow por defecto) expuesto como control vivo en la demo. Verificado con Playwright: glow pintado desde el token de paleta, 4 Reveal con índices estructurales 0·1·2·3 a 70ms, titular H1 a 80px, y el patrón `grid` con su máscara. `blocks:check` verde (7 blocks) · `vitest src/uix/eidos` 361/361 · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
2 months ago |
|
|
54b8b838e7 |
feat(blocks): F2.6 `faq` — proxy fino del Accordion del canon
La sección de preguntas: una cabecera sobre un acordeón de P/R en columna estrecha. Compound (`.Item` repite) y —la regla al envolver un componente interactivo— un PROXY FINO del `Accordion` del canon: el block lee su API y la pasa tal cual, no reinventa el disclosure. `<Faq>` + `.Header` + `.List` + `.Item`. - `.List` **ES** el `Accordion`: toda su API pasa sin gate — `type` (single/multiple), `bind:value`, `collapsible`, `variant`, `size`. Defaults de FAQ: single, collapsible (el abierto se puede cerrar), outline. - `.Item` proxya `Accordion.Item > Header > Trigger`(snippet `question`) / `Content`(children) y autogenera el `value` (clave de estado) con `$props.id()` si no se pasa — la única conveniencia sobre el andamiaje. - El teclado (flechas, Home/End, Enter/Espacio), `aria-expanded` y `aria-controls` salen del `Accordion`; el block no toca la a11y del disclosure. `Container` estrecho (`md`) para una columna legible. Demo (`web/routes/blocks/faq/`): full-bleed + ruta `preview`, cinco preguntas en una columna, con la cola «¿aún tienes dudas?» que la app pone tras `.List`. Huecos a decisión del usuario (Gaps): la **lista estática 2/3 columnas** (6 de 7 en TW NO son acordeón, sino P/R siempre abiertas) — otra disposición, prop `layout` o hermano. Verificado en navegador: el acordeón abre/cierra, claro/oscuro × LTR/RTL. `blocks:check` verde (7 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
b59332132f |
feat(blocks): F2.5 `testimonials` — rejilla de citas con autor
Prueba social: una cabecera sobre una rejilla responsive de tarjetas de cita, cada una con su autor (avatar · nombre · cargo). Compound —la `.Item` se repite, como feature-grid— y SIN contexto (las partes no coordinan). `<Testimonials>` + `.Header` + `.Items` + `.Item` + `.Quote` + `.Author`(slot `avatar` + `.AuthorName`/`.AuthorRole`). - `AutoGrid` de tarjetas de igual alto; `.Author` fijada al borde inferior de la tarjeta (`margin-block-start: auto`) para que una fila de citas de distinto largo alinee las caras. - La cara es del app: un `<Avatar>` con imagen o un `Avatar.Fallback` de iniciales va en el slot `avatar` (B-7). - La tarjeta del quote usa `variant="outline"` — el `soft neutral` es casi invisible en claro. Encontrado al componer (en el README del block): el tema activa solo un SUBCONJUNTO de escalas donor (`green/indigo/orange/plum/teal`); una escala no activada (`cyan/ruby/amber/jade`) en `color` cae en SILENCIO a `primary` (la regla `[data-color]` hace `var(--scale-…, primary)`). Config del tema, no bug de componente; la demo usa escalas activadas. Hueco a decisión del usuario (Gaps): la **cita única en spotlight** (grande, centrada, logo+avatar+autor) — la variante modal del dossier, el grid es minoría. Otra disposición → candidato a hermano `testimonial-spotlight`. Demo (`web/routes/blocks/testimonials/`): full-bleed + ruta `preview`, cinco citas con avatares de iniciales en cinco colores distintos. Verificado en claro/oscuro × LTR/RTL, caras alineadas al fondo. `blocks:check` verde (6 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
594e5a5db4 |
feat(blocks): F2.4 `pricing` — el primer compound CON CONTEXTO del tier
La sección de planes: un toggle de periodo sobre una fila de planes, uno destacado. Es el primer block cuyas partes se COORDINAN de verdad (no solo se repiten como en feature-grid/split): el `Switch` escribe el periodo de facturación y cada `PlanPrice` lo lee. Esa coordinación es lo que gana un contexto compartido — la forma más fuerte de compound. `<Pricing bind:period>` + `.Header` + `.Switch` + `.Plans` + `.Plan`(featured, badge) + `.PlanName`/`.PlanDescription`/`.PlanPrice`/`.PlanFeatures`/ `.PlanFeature`/`.PlanAction`. - **Contexto reactivo** (`context.ts`): la raíz provee el periodo como getter sobre un `$bindable`; el `Switch` (un `ToggleGroup`) lo escribe, el `PlanPrice` lo lee y muestra el snippet `monthly` o `annual`. Mismo patrón que `CardGroup`. `period` es bindable por si la app quiere observarlo. - **El block NUNCA formatea moneda**: la app compone `FormatNumber` dentro de los snippets de precio (B-7). El block posee el switch, no el dinero. - `.PlanAction` fija el CTA al borde inferior de la tarjeta (`margin-block-start: auto`) para que una fila de planes alinee sus botones aunque tengan distinto nº de features; `.Plan` con `align="start"` deja los checks en columna limpia; `featured` da acento (borde primary) + elevación. Demo (`web/routes/blocks/pricing/`): full-bleed + ruta `preview`, tres planes (Pro destacado en el centro) con el toggle mensual/anual vivo. Hueco a decisión del usuario (Gaps del block): la **tabla de comparación** (features × planes) — la brecha recurrente del dossier en pricing. Es una tabla, no una fila de tarjetas: candidato a hermano `pricing-table`. Presentado, no resuelto. Verificado en navegador: el toggle cambia los TRES precios a la vez (0/29/99 → 0/23/79), tarjetas de igual alto con CTAs alineados, featured con acento, en claro/oscuro × LTR/RTL. `blocks:check` verde (5 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
08ea82d128 |
feat(blocks): F2.3b `feature-split` — la brecha nº1 del dossier, con Mockup
La sección que las 5 refs shippean y que va justo tras el hero: una afirmación de producto junto a un screenshot, alternando lados fila a fila. Otra disposición que la rejilla de iconos de `feature-grid`, así que block hermano (decisión del usuario), no una variante turbia dentro de aquél. Compound —la `.Row` se repite—: `<FeatureSplit>` + `.Row` (`reversed`, slot `media`) + `.Eyebrow` + `.Title` + `.Text` + `.Features`/`.Feature` (checklist) + `.Actions`. El block coloca; la app pone la copy y la media. - `reversed` mueve la media al lado de inicio vía `grid-column` (Box expone `gridColumn`/`order`), dejando la copy SIEMPRE primera en el DOM — el orden de lectura y el foco no cambian aunque el screenshot salte de lado. - El check de cada `.Feature` es decorativo (`aria-hidden`): la palabra lleva el significado. - La media se COMPONE con el primitivo `Mockup`: la demo enseña cromo de navegador (dashboard) y de teléfono (app), cerrando el hueco de «tratamiento de media» del dossier de raíz en vez de falsearlo. `.Row` usa un tipo limpio (no `HTMLAttributes`) porque su slot `media` colisiona con el atributo HTML homónimo; el resto de sub-partes extienden los props del componente canon que envuelven (lección de feature-grid). Demo (`web/routes/blocks/feature-split/`): full-bleed + ruta `preview`, con control del nº de filas y del lado inicial; 3 filas con Mockup navegador/teléfono. Verificado en navegador (Playwright, módulos frescos): filas alternas en claro/oscuro × LTR/RTL, `reversed`, el orden de lectura copy-primero, y los dos cromos de Mockup. `blocks:check` verde (4 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
d01b3e6af5 |
feat(blocks): F2.3 `feature-grid` — el primer block compound del tier
`<FeatureGrid>` + `.Header` + `.Items` + `.Item` + `.ItemIcon`/`.ItemTitle`/ `.ItemText`: una cabecera sobre una rejilla responsive de features (icono · título · texto). Es la sección que responde «qué hace», la nº1 tras el hero. Primer block COMPOUND del tier, y aplica la regla de forma que fijamos: el `.Item` se REPITE (el app mapea sobre N features) → gana sub-componentes, donde `hero`/`site-header` usan slots de snippet (partes fijas de layout). Las partes no coordinan —sin contexto ni estado entre ellas—: la rejilla es del padre, las celdas del app. `.Items` es el envoltorio honesto de la rejilla (`AutoGrid`), que deja la cabecera fuera sin un `grid-column: 1/-1` a pelo. - El block coloca (Section · Container · AutoGrid · Surface · Heading · Text); el app pone todo el contenido por children (B-7). - `.Items` fluido por `minChildWidth` (tantas columnas como quepan) o `columns` fijas; `.Item` `align` start/center; `.ItemIcon` chip `Surface`; `.ItemTitle` `Heading` h3; `.ItemText` `Text` apagado. - `align` de sección (center/start) coloca la cabecera coherente con las columnas. Dos cosas encontradas al construir, resueltas: - **Los sub-componentes de bloque extienden los props del componente canon que envuelven** (`BoxProps`, `StackProps`, `HeadingProps`…), **no `HTMLAttributes`**: el `style: string|null` del atributo HTML crudo choca con el `style: string` del canon al hacer spread (+ "union type too complex"). - **`.ItemIcon` por defecto `solid`, no `soft`**: el soft-primary en claro es casi blanco (oklch 0.99) → el chip era invisible; solid da el chip con glifo on-solid (la tinta de contraste la pone `Surface`). Demo (`web/routes/blocks/feature-grid/`): full-bleed + ruta `preview`, con control de columnas (fluido/2/3/4), align, nº de items y dir; 6 features con iconos del canon. Hueco a decisión del usuario (en los Gaps del block): el **feature-split/ alternante** (texto junto a un screenshot, lados alternos) — la brecha nº1 del dossier — es otra disposición (filas de 2 columnas, no rejilla de iconos): probablemente un block hermano `feature-split`. Presentado, no resuelto. Verificado en navegador (Playwright, módulos frescos): center/start × claro/ oscuro × LTR/RTL, columnas fluidas y fijas, 3/4/6 items, chips visibles. `blocks:check` verde (3 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
5125502486 |
feat(blocks): F2.2 `hero` — center · split · background, por slots
Segundo block del tier. API por SLOTS DE SNIPPET, no compound: la forma se razonó con el usuario y con el paisaje de referencia. Compound se reserva para partes que COORDINAN (estado/contexto/ARIA entre ellas — Accordion, Dialog); un hero son cinco slots de layout que no se hablan entre sí y que el root arregla, así que snippets. Además el campo entero shippea marketing como copy-paste plano —nadie aplica compound a una sección—, y los slots por zona son la historia de personalización que distingue al tier del copy-paste. `<Hero layout="center|split|background" level container size>` + `eyebrow/title/description/actions/media/background/children`. - El block **envuelve** título y subtítulo en `Heading`/`Text`: así posee el `id` que nombra el `<section aria-labelledby>` y el nivel del encabezado, mientras la app pone las palabras (B-7). `eyebrow`/`actions`/`media` son contenido libre (ahí los componentes del canon SON la API). - `center` = `Stack` centrado, media debajo (ancho-capado); `split` = `Grid` de dos columnas (una sola sin media), apila en estrecho. - **`background` (cover)** — añadido por scope-approval del usuario: la media a sangre detrás de la copy, con velo de contraste (`--color-overlay` a `--opacity-scrim`), texto `on-solid` y `object-fit: cover` vía un `<style>` justificado (D-BLK.2). Son las ÚNICAS reglas que el block posee, todas sobre tokens del ecosistema — cero color a mano. Capas por orden de fuente, sin `z-index`. Demo (`web/routes/blocks/hero/`): full-bleed en la página + ruta `preview` para anchos de dispositivo, cada prop un control vivo, y el backdrop del layout cover dogfooda el sistema de color — es un `Surface color="primary" gradient` (finish aurora = `--gradient-aurora`, derivado de los roles del tema; cambia con la paleta). Mini-site compartido por las dos superficies (`HeroSite.svelte`). Encontrado al componer, registrado no resuelto: `Box`/`Surface` `flex`/`grow` no hicieron crecer un hijo flex (bars a 0-width, `flex: 0 1 auto`; la prop no la usa ningún componente shipped). La demo usó `Grid` (tracks `1fr`). Flag en el README del block y en el handoff para revisar el cableado de `--box-flex`. Verificado en navegador (Playwright headless, módulos frescos): center/split/ background × claro/oscuro × LTR/RTL, media on/off, y el landmark nombrado (región con `aria-labelledby` que resuelve al título). `blocks:check` verde (2 blocks) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
356ebf92af |
feat(button): el CTA que navega y parece botón, por composición (opción D)
El hueco que registró `site-header`: un «Empezar gratis» de cabecera tiene que
NAVEGAR y parecer botón. `Button` no crece un `href` —`Link` posee la
navegación, y un ancla se activa con Enter, no con espacio, lo cual es
correcto—: presta la pintura por composición. Lo que hacía de esa forma un
downgrade era que el `child` (asChild) descartaba la decoración; se completa el
slot.
- **eidos `<Button>`**: el `child` recibe ahora `content`, el cuerpo YA
decorado (icono · etiqueta · endIcon · spinner) en su propio snippet que
comparten las dos ramas de render. Así
`<a href {...props}>{@render content()}</a>` conserva TODOS los slots en vez
de sustituirlos (antes la flecha del sitio alpha estaba escrita a mano).
Nuevo tipo exportado `ButtonChildProps`.
- **soma / morfo**: en la forma `child` el elemento es del consumidor, así que
soma deja de estampar `type` (un `<a type="button">` es una pista de MIME
falsa). El componente pasa `type: undefined` cuando hay `child`; el provider
lo REENVÍA verbatim (antes lo re-defaulteaba a `'button'` y pisaba el drop —
el default vive en el destructure del componente); el morfo declara el attr
`type` condicional (`prop-truthy`).
- **docs**: ejemplo rancio de `index.ts` corregido (anunciaba un `asChild`/
`variant="link"` que no existen); sección «CTA que navega» en el README de
eidos con el patrón y el footgun documentado (un `<button>` en un `<form>` vía
`child` se pone su propio `type`); nota en el README de soma.
- **site-header**: el CTA de la demo usa ya la forma real (`<a>` sólido con
flecha), y el hueco pasa de «candidato a canon» a CERRADO por composición —
`Button` sigue sin `href` y `Link` sigue poseyendo la navegación, las dos
decisiones firmadas se mantienen. Actualizados PLAN/CONTINUE-blocks.
- **demo de Button**: control `child (asChild → <a>)` vivo, snippet del código
y fila de a11y explicando por qué el ancla activa solo con Enter.
Verificado en navegador (dev, restart para módulos frescos): asChild ON →
`<a href="#pricing">` sin `type`, pintura sólida completa (bg primary, tinta
blanca, 36px, padding 16px), y el slot de icono SOBREVIVE dentro del ancla
(`[data-button-icon]` + svg + body); asChild OFF → `<button type="button">`
intacto (sin regresión de submit implícito); el CTA real de `site-header` sale
`<a>` con la flecha final y 0 errores de consola. `blocks:check` verde ·
`vitest src/uix/morfo` 114/114 · `svelte-check` sin errores propios nuevos.
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 |
|
|
ee02c5c55f |
docs(agent): D-AG.12 y §5b firmadas — la spec, y quién prueba el eje
Dos decisiones que estaban pendientes, firmadas tras investigación a fondo
(precedente externo verificado a fecha, no de memoria).
D-AG.12 — EL CONTRATO COMO REFERENCIA
Spec normativa en `docs/spec/` (RFC-2119 + IDs `AG-n` + escalera
stable|provisional|reserved + versionado por fecha), en estado DRAFT para que
F4b se construya CONTRA ella y no al revés. La doctrina se queda con el PORQUÉ.
Conformance en DOS vehículos, que es lo que evita que la palabra sea retórica:
kit de fixtures del protocolo (PORTABLE — un tercero lo corre contra SU
implementación; semilla ScriptedAgentTransport) + agent-check (lint del repo),
ambos citando IDs de requisito en sus fallos. Las dos claims de frontera se
nombran con marcador claimed|shipped porque hoy ninguna está materializada.
Gate de promoción a 1.0: ≥2 formas de dominio + claims shipped + fixtures
verdes. Respaldo externo: MCP ya no promueve a Final sin escenario en su suite
(SEP-2484) y opera lifecycle formal con ventana de 12 meses; AG-UI se volvió
referencia por ADOPCIÓN (AWS Bedrock AgentCore, Microsoft Agent Framework), no
por publicar suite. La spec se escribe antes para gobernar; el título se gana
después.
§5b — QUIÉN PRUEBA EL EJE: form + table
El gate de F4 pedía «2 manifiestos reales» sin asignar dueños; esto rellena ese
hueco, no enmienda nada. El criterio deja de ser disponibilidad y pasa a ser
dificultad: un formato lo decide su caso peor. Y form+table no son preferencia,
son la ANATOMÍA del contrato — los args de una capacidad SON schemas (sium →
toJsonSchema, ya construido en F1) y un run ES una transacción sobre un
conjunto de ediciones. Form además dobla: agente→componente (args contra
campos) y componente→agente (elicitación) son la misma proyección en los dos
sentidos.
Chat y palabras quedan como CONSUMIDORES, no jueces. El riesgo que esto evita
era real: ambos son texto lineal, así que un manifiesto probado solo ahí
habría asumido texto en silencio (rango como selección, ediciones
secuenciales, args string) y el caso peor habría aparecido tarde. Inventario
al firmar: 170 morfos, 0 con capacidad delegate declarada.
COROLARIO — la elicitación no depende de superficie conversacional
Una pregunta tipada es un formulario en contexto (Form.AutoFields), descartable
y no-modal, que es justo lo que exige el contrato a11y (WCAG 2.2.4). La nota de
protocol.ts («materializes with the conversation surface») describía UNA opción
y se leía como necesidad: corregida ahí y en la doctrina §7.
DESCARTADO
Entrada de delegación global siempre-disponible: roza el modo ambiental, que
está FIRMADO FUERA (⚖️1), y no se recupera por la puerta de atrás. La entrada
es un patrón con pieles montado en app-land — mismo veredicto que la
review-card.
Handoff `continue-agente-2026-07.md` reescrito para mañana: la spec DRAFT es lo
primero, luego el kit de fixtures, luego F4b con los dueños ya firmados.
Verificado: agent 30/30 · agent:check limpio · docs:check sin errores nuevos
(el único es el preexistente ajeno de callout).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
6b23aa7ef1 |
agent(guard): agent-check nace con la auditoría que ya tiene sujeto — anti-falsificación de actor
D-AG.11 (firmada) define SEIS auditorías. Cinco necesitan el árbol de manifiestos `src/uix/agent/components/`, que es trabajo de F4b — y el plan es explícito: el guard «nace CON el árbol, no después». Así que quedan DECLARADAS en el script y se reportan como pendientes: añadir un manifiesto las enciende, y el guard nunca puede ir por detrás de la superficie que vigila. La quinta SÍ tiene sujeto hoy y no la vigilaba nadie: F1 entregó el primitivo de actor (`$libs/actor` + el registro WeakMap privado del motor) y su invariante — que SOLO el motor acuña — no estaba protegida por nada. El token es una marca de compilación sin constructor en runtime: un forjado resuelve a null, pero un `as ActorToken` TIPA, y un contexto de actor construido a mano en el canon es exactamente la suplantación que el eje prohíbe (agent.md §5). Por eso D-AG.8 §2 dice literalmente que su defensa es el lint de agent-check, no el sistema de tipos. Dos reglas, sobre canon (uix · arts · libs · svrs · packs · web) excluyendo el arte agente: - `actor-forgery` — cast a ActorToken fuera de $agent - `actor-handbuilt` — `actor:` con valor literal (objeto/string/número) en vez de un token RECIBIDO; reenviar (`actor: opts.actor`) es la costura sancionada y calla. Los tests quedan exentos: ahí fabricar un forjado es precisamente lo que se prueba. Verificado en negativo, que un guard que nunca falla es teatro: inyectado un cast + un contexto a mano en `sema/signal.ts` → 2 hallazgos con fichero:línea y exit 1; revertido y de vuelta a limpio. `npm run agent:check` cableado junto a los demás guards del repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
528af6f72d |
docs(blocks): F1 CERRADA (8/8) · registro de sidebar y su review
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
59f20e2b1f |
uix(aura): el Status en dos líneas — cierra D1
La otra mitad de la dirección firmada: el anillo dice el estado como FORMA, pero la respuesta a «¿quién actúa?» es texto, y una palabra gris de 13px junto a un orbe vivo no la daba. Ahora el Status son dos renglones: Esperando tu revisión ← el estado, content-primary, peso medium la custodia es tuya ← qué significa para ti, secondary, un escalón menos Sin parte nueva: el Status ya existía y añadir un `Detail` habría sido abstracción prematura (y una pelea de layout — dos hermanos no apilan sin envoltorio). El soma renderiza las dos líneas por defecto y expone `detailText` en los snippet props, así que un consumidor que traiga sus children sigue mandando. La copia del detalle es FIJA por estado y localizada como la etiqueta (es/en): los contadores reales del run (turnos / actos) NO están en el puerto estructural, y ensancharlo para un subtítulo más bonito es una decisión, no un defecto. Queda dicho en el README por si algún día se firma. Tokens: `--aura-status-color` pasa a `content-primary` (era secondary: el peso informativo estaba en el sitio equivocado), + `-weight`, `-gap` y `-detail-color`. Verificado en Chrome real, claro y oscuro, con un run en `reviewing`: peso 500, primary sobre secondary, y las dos líneas escalando con `data-size`. Tests 478, eidos-lint 0 invalid / 0 class-hooks, morfo:vocabulary 0 errores, check en los 73 ajenos preexistentes. Con esto D1 queda cerrada entera (anillo + jerarquía). Handoff al día. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
e10eed98cc |
uix(aura): la superficie visible del agente — anillo de custodia, orbe-contorno y desenlaces expresados
Rediseño dirigido por el usuario sobre un banco de mockups con el orbe REAL
corriendo dentro. Seis frentes, todos con maquinaria existente:
ANILLO DE CUSTODIA (parte nueva `Aura.Ring`, morfo+soma+eidos)
El estado se lee como FORMA y no solo como tinte: raíl punteado en reposo,
arco que respira al ofrecerse, segmentos = pasos del plan (laten), dos arcos
enfrentados y QUIETOS esperando tu revisión (la quietud lo separa de acting),
arco que gira actuando, anillo cerrado + pulso saliente cuando te necesita.
Envuelve al Indicator (es su marco de referencia, no un hermano posicionado a
mano). El movimiento continuo son loops REGISTRADOS (data-animation-style),
nunca @keyframes de receta (R-4.5) — vienen con su puerta de reduced-motion,
donde el dash sigue distinguiendo el estado. El attr va en los hijos del SVG,
jamás en la parte: motionAttrs() estampa además data-state, del morfo ahí.
EL ORBE ES UN CONTORNO
`thickness` en el efecto orb; el hueco es real (alfa propia) sobre cualquier
fondo. Se va la rama claro/oscuro y el parámetro backgroundColor: el shader
mezclaba con el fondo y derivaba el alfa del canal máximo, o sea pintaba un
disco OPACO del color de la superficie justo en el centro (en claro un círculo
relleno; en oscuro el azul del seed). Y la receta deja de pintar su disco en
cuanto monta el canvas (:has(canvas)) — solo sobrevive donde no hay render
vivo: SSR, tests, effect={false}, sin WebGL.
COLOR = TOKENS DE ROL, PINTADO Y NO ROTADO
colorA/colorB/colorDeep (solid / solid-hover / element) resueltos con
eidos.resolveToken y pasados como hex, que es el seam que el propio arte
documenta. El hue-delta anterior se medía en OKLCH y se aplicaba en YIQ: risk
salía VERDE junto a su propio anillo naranja.
ONION EN EL IDIOMA DEL ORBE
Prop `look="solid" | "luminous"` (defecto solid: nadie más se entera). En
luminous el sector no tiene relleno — filo de luz (pasada borrosa + nítida) y
etiqueta en el color del sector; la cuña sigue siendo área táctil. El trigger
con snippet propio va BARE: sin disco, sin sombra FAB y sin el salto del hover
(el orbe ES el trigger, no su contenido). Medio anillo bottom-center y radios
44/40. El halo respira (loop pulse) y en commit-select da UNA pasada de ping
pivotando en el centro real del menú — la firma genérica de commit anima
box-shadow, que sobre un path de SVG no hace nada.
DESENLACES EXPRESADOS (el hueco real: un fallo era indistinguible de un éxito)
Tres eventos terminales con intent INTRÍNSECO — delegate-return (fulfill) /
-aborted (sin intent: un decline es ausencia, no pérdida) / -failed (loss) —
en vez de un intent dinámico, que no tiene dónde vivir: TriggerOptions no
lleva intent y el fromProp del morfo ata a un prop PÚBLICO, no a estado de
máquina. Puerto `runs?` opcional en AuraAgentSource porque activeRun ya es
null al cerrar (§0: sin él, anuncio genérico). Primeras firmas de la familia
delegate en el registro; la de loss es MOVIMIENTO (return-recoil: retirada +
descenso), no tinte — no estrena ningún hue.
MAPA SEMÁNTICO DE COLOR (demo)
Un hue = un significado en toda la superficie: gris=sin agencia ·
naranja=tu revisión · morado=él actúa · rojo=escalated (exclusivo) ·
teal=tu sí. El anillo de autoridad reusa los colores de la custodia con el
MISMO sentido (Review=risk porque vivirás en reviewing; Auto=primary porque
actúa) y los declines pasan a neutral — canon: el intent de un cancel es
`absent`. Además el anillo en reposo DELEGA (las tres autoridades abren el
run) y se cierra por transición, no siempre que no hay run.
Dos guardas del framework corrigieron el diseño por el camino:
- BK-FRAME-NO-INTENT rechazó dar intent a una familia de marco sin
justificación escrita (excepción A-1): queda el intentRationale — el motor
no emite nada y no hay evento evaluable que enmarcar, así que el retorno
consolidado es el ÚNICO sitio expresivo del desenlace.
- fallbackTarget gana sobre el target del morfo (contrato de TriggerOptions):
el sello caía en el provider. Los terminales pasan el Ring cuando existe.
Verificado en Chrome real (ventana delante: en segundo plano el navegador
congela rAF y ResizeObserver y las medidas mienten): seis estados en claro y
oscuro, y un error de transporte REAL apuntando a un puerto muerto →
delegate-return-failed · loss · return-recoil sobre el Ring · región
«Agente: falló — control devuelto».
Tests 478 (morfo 114 · soma/aura 11, +5 casos de desenlace · eidos 353),
eidos-lint 0 invalid / 0 class-hooks nuevos, morfo:vocabulary limpio,
check en los 73 errores ajenos preexistentes.
Pendiente (handoff actualizado): la jerarquía tipográfica del Status (D1
llevaba dos líneas y solo se hizo el anillo) · el pulso de commit-select se
corta si el consumidor cierra el anillo al elegir (necesita retención tipo
Presence) · D5 la superficie de composición del encargo, decisión abierta.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
e28df1817f |
docs(blocks): registro F1.7 fase morfo+soma en el plan
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
ef6bd13be3 |
uix(nav-tree): `disabled` por nodo — destino no disponible
Decisión del usuario. Un nodo deshabilitado conserva su fila (sigue siendo parte del mapa) pero pierde el `href`: no hay nada que activar con clic, Enter ni «abrir en pestaña nueva». Un `pointer-events: none` de CSS solo habría tapado el ratón. - morfo: la parte `link` gana `data-disabled` (propRef) y `aria-disabled` (literal `true` bajo `prop-truthy: disabled`). - soma: `disabled` solo aplica con `href` (un grupo sin destino lo ignora); el toggle del grupo NUNCA se deshabilita — mostrar/ocultar es control de vista, no destino. - eidos: fila en `--color-content-disabled`, sin puntero y sin hover. - demo: «Agent» va deshabilitado con badge «Soon» + filas de API y a11y. Verificado en navegador real: `<a>` sin href, `aria-disabled="true"`, `data-disabled`, color 0.79 frente a 0.61 de una fila normal, clic sin efecto, fuera del orden de tabulación (44 focusables en vez de 45), badge dentro del control. `component:audit` PASS · eidos-lint 28 morfo-backed / 0 invalid · `vitest src/uix/eidos` 353/353 · svelte-check 0 errores míos. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
a7e629f9ed |
uix(nav-tree): F1.8 CERRADA · demo + navegador + review adversarial
Cierra el árbol de navegación data-driven (E-1) de F1: demo canónica de 9
pestañas con el mapa real de docs (43 nodos, 3 niveles), verificación en
navegador real y review adversarial (5 dimensiones × 3 verificadores
escépticos; 22 hallazgos brutos, 10 confirmados) con todos los confirmados
arreglados.
Arreglos del review
- sema: la parte `group` —target de los eventos emerge— se registraba SIN
`ref`, así que `runtime.trigger` lanzaba `SomaRuntimeTargetError` en
silencio y el pack no sonaba nunca (cero `data-event-*` en el grupo frente
a los de collapsible). El provider posee ahora el ref del `<ul>`.
- eidos: en una fila navegable el chevron resolvía `inline-size: 100%` como
flex-basis y ocupaba media fila (101 de 231 px en «Soma»), robándole clics
al enlace. Toggle compacto con suelo de diana de 24 px (WCAG 2.5.8).
- soma: una clave duplicada podía volver cíclico `parentByKey` y colgar la
pestaña dentro de `trailKeys` (deriva en render) → clave sufijada + aviso
del logger + guarda de ciclo en el paseo.
- soma: el colapso es CONTEXTUAL (recuerda el `activeKey` bajo el que se
hizo): cerrar la sección que lees se respeta, pero caduca al navegar
DENTRO del grupo, para que la página actual nunca quede sin fila visible.
Sigue siendo query pura, sin `$effect` que escriba estado.
- soma: `child` recibe también `children` (el árbol renderizado); antes
dejaba el landmark vacío, porque un árbol data-driven no lo puede
reautorar el consumidor.
- morfo + langs: el nombre accesible del chevron se declara en el contrato y
se localiza («Alternar sección {label}»); ya no duplica el del enlace.
- eidos: RTL completo — el glyph espeja solo (bordes lógicos), lo que no
espeja es el giro, así que bajo `[dir='rtl']` las dos rotaciones se
intercambian. El Gap «dirección del chevron en RTL» queda RESUELTO.
- demo: paridad de snippet con los controles vivos; fuera el token fantasma
`--nav-tree-rail-width` del docblock del recipe.
badge en v1 (decisión del usuario, delegada)
Está en el suelo de paridad del dossier §P5 (los 6 refs lo llevan). Se
resuelve con SNIPPET, no con recursión a nivel de eidos: el morfo declara la
parte `badge`, soma renderiza el snippet recibido (sin él, el valor crudo —
sigue siendo headless) y el wrapper de eidos pasa el `Badge` canónico. Va
DENTRO del control de la fila, así su texto entra en el nombre accesible
(«TSC, New, enlace»). `disabled` se descarta en v1 (fuera del suelo, y un
enlace de navegación deshabilitado es semánticamente dudoso); ambos quedan
registrados en la tabla de Gaps.
Verificado: `component:audit` PASS · eidos-lint 26 morfo-backed / 0 invalid ·
`svelte-check` 0 errores en estos archivos · `vitest src/uix/eidos` 353/353 ·
navegador real (Playwright): trail auto-expandido, sema estampando en el
grupo, teclado nativo, foco visible, claro y oscuro, RTL, 375 px sin
desbordes, 0 errores de consola.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
549c5e5abd |
docs(agent): D-AG.12 pendiente de firma — el contrato como referencia
PLAN §5: la decisión editorial planteada con el usuario (2026-07-22) queda firmable al retomar — (a) elevar agent.md a especificación publicable («el contrato de delegación») con agent-check como suite de conformance y el checklist de amenazas como anexo; (b) nombrar las dos reclamaciones de frontera que F4/F2 ya materializan: contexto por atención y delegación como transacción; (c) posición honesta declarada (acts secuenciales v1, cliente-first, generalidad pendiente del 2º/3º consumidor). continue-agente: punto 0 del orden de trabajo → firmar D-AG.12. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
e835163103 |
uix(nav-tree): F1.8 WIP · data-driven docs nav tree (soma+sema+eidos)
Complete component, green on all static gates (audit PASS, eidos-lint 20/0, svelte-check 0, contracts clean for its parts). NOT tree-view: APG Disclosure Navigation (<nav> landmark + native links + disclosure groups), data-driven (E-1) — the app passes `nodes` + `activeHref`, the provider resolves the active node and auto-expands its ancestor trail. - morfo: nav/list/item/trigger/link/group; emerge-expand/collapse events - soma: NavTreeProvider (shared state, pure isExpanded query) + per-node NavTreeItemProvider (menubar pattern, recursive render) - sema pack: soft emerge on group disclosure (mirrors collapsible) - eidos: rail + per-depth indent recipe; own bespoke rows (not composed Link), Badge deferred to a Gap; aria-current="page" from data-active Remaining before F1.8 closes: demo + browser verify + adversarial review. Handoff: docs/process/CONTINUE-nav-tree.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
1cc8a87722 |
docs(agent): continue-agente — handoff para la próxima sesión
Estado al cierre (F0/F1/F3/F4a cerradas + tabla de commits), verificación rápida al retomar, siguiente trabajo en orden (F4b chat → F2 gated → flecos), y la disciplina operativa de la rama compartida + gotchas de entorno (Ollama/OpenWebUI, pane, Chrome MCP, index:0 del stream). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
0ab40cdcee |
uix(agnt): Aura al centro de un OnionMenu — afordancias radiales por estado
La composición que pidió el usuario: el orb es el trigger central de un OnionMenu placement="center" y las afordancias de custodia aparecen como sectores contextuales — reviewing→Authorize/Reject, escalated→Approve/Deny, activo→Stop — sin fila de botones. El anillo se abre SOLO mientras la custodia espera a un humano, con re-aserción (un cierre externo sin resolver re-abre: el efecto lee onionOpen como dependencia); por click en el centro mientras actúa; se pliega al devolver el control. App-land por doctrina (agent.md §7): el framework da estados + afordancias (Aura) y componentes (OnionMenu); el app da forma a la superficie de review. Patrón documentado en el README de aura (§Patrón). E2E verificado en Chrome real contra qwen3-coder: la escalada real por doc.clear abre el anillo con el orb rojo al centro → Deny en el sector → returned(aborted, user-cancel) + «Agente: terminó — control devuelto» + documento intacto. check 0 propios · smoke agnt PASS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
595040b73b |
feat(agnt): Aura como superficie de presencia de la demo con modelo real
La respuesta a «¿está pensando?» no se improvisa — es Aura, el componente
del eje construido para ello. La sección real de la página agnt bootea un
uix standalone mínimo (createActiveUix + Soma + ActiveEidos + eidos css):
legítimo porque un app compone ambos tiers, y la demo ES el app.
- Motor llama → createActiveAgent (mismas opciones bus+journal); activeRun
reactivo satisface AuraAgentSource; stream vía .engine.onEvent
- <Aura {agent}> con Indicator (orb $scene) + Status + Region + Cancel;
authorize/escalation quedan como afordancias app-level (doctrina §7)
- Verificado sobre el run real de qwen3-coder: «Inactivo» → «Propone un
plan» → announcement «Agente: terminó — control devuelto» en la Region,
doc mutado, answer en es; Cancel = Button disabled-en-idle; consola 0
check 0 propios · agent 30/30 · smoke agnt PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
447a87239c |
feat(agent): adaptador openai-chat real + carril de iniciativa del bus
El bus como eje del agente a nivel de app, cerrado en tres direcciones:
observar (agent.*), reaccionar (orca) e INICIAR — hecho de dominio → acción
orca → App.agent.start({initiative:'system'}) con clamp auto→review por
construcción. Nunca command-events. Sección doctrinal + ejemplo en la página.
Adaptador real `$agent/adapters/openai-chat` (fuera del barrel, D-AG.1c):
dialecto OpenAI chat-completions (Ollama/llama.cpp/LM Studio/proxy), SSE →
protocolo v1, sanitización bidireccional de capability ids con puntos, y
re-pairing de tool_calls al reproducir el transcript (memoria por callId).
Fix cazado en vivo: Ollama reutiliza index:0 para todas las calls del
stream — una call nueva se detecta por id distinto, jamás por index (los
args concatenados producían invalid-args en motor + 400 en el replay).
Demo viva en /active/docs/agnt contra qwen3-coder:30b real: capacidades
doc.* tipadas (sium), read snapshot-por-turno, streaming, escalada
irreversible. Ciclo completo verificado en navegador: 3 acts, 2 turnos,
doc mutado, respuesta en es, rastro entero en bus (correlationId=runId).
agent 30/30 · smoke agnt PASS · check 0 propios.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
fa71890564 |
fix(agent): conformidad bus/orca — payload del bus sin contenido de usuario
Auditoría punto-a-punto contra los README de bus y orca (12/13 conforme).
La no-conformidad real: `goal` viajaba en AgentRunBusPayload, contra la
higiene documentada de ambos (bus: «pasa correlationId y que el suscriptor
resuelva el contexto sensible del módulo»; orca: «no unnecessary private
data — la acción lo resuelve del módulo al ejecutar la acción»).
- AgentRunBusPayload = SOLO metadata (runId/initiative/autonomy/state/
outcome/reason/acts/turns); el suscriptor resuelve vía App.agent.run(runId)
- spine test prueba el patrón: 'goal' in payload === false + resolución
desde el módulo con el orca REAL
- página agnt: nota de higiene + recomendación configureEvent(
'agent.run.state', { queuePolicy: ORCA_QUEUE_REPLACE_QUEUED }) para el
evento frecuente (doctrina de colas de orca)
- PLAN-agent F4a: acta de la auditoría de conformidad
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
803b588ba5 |
feat(agent): F4a · marco de integración app — bus/orca/página del arte + fixes de auditoría
Auditoría del track (encargo): divergencias firmadas-sin-implementar corregidas y el marco de integración INDEPENDIENTE del componente — el agente como servicio del app, no como demo de un componente. Fixes de decisiones firmadas (con tests): - C3/D-AG.7: la iniciativa autónoma (system/scheduled) clampa auto→review — un run que el usuario no pidió jamás se auto-autoriza. - D-AG.5: presupuesto de contexto (maxContextBytes) con FIFO-trim byte-based (el goal sobrevive; resumen semántico diferido) + log del recorte. - F4c: tope por-arg (maxArgBytes) → invalid-args tipado. - D-AG.6d: schema Standard-Schema del resultado (`result?`); un mismatch vuelve como recoverable para auto-corrección y no cuenta como act. Integración app (patrón session EXACTO): - El arte posee el vocabulario `agent.*` (consts) + bus-helpers tipados que publican con bus INYECTADO (type-only $libs/bus): run.opened/state/ escalated/closed + act.applied, con source='agent' y correlationId=runId — la costura causal ligera (una delegación correlaciona de punta a punta). - defineActiveAgent cablea core.bus (junto a timers/logger). - Reacciones vía orca APP-LAND (el invariante intacto): probado con EngineOrca REAL reaccionando a agent.run.closed en integration.test. - Página del arte /active/docs/agnt (la convención que faltaba): sección Delegation en nav; artículo + playground VIVO con los 4 motores puros reales (bus log en vivo, journal WAL, contador orca, kill switch, compuertas deterministas) — cero componentes, integración pura de app. Gates: agent 26/26 · smoke agnt PASS · check 0 propios · verificado en navegador (pane + Chrome real): ciclo completo, orca archiva runs, kill switch reactivo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
2803d5dddc |
docs(blocks): registro F1.6 anchor-nav · F1 = 6/8
Adversarial-review outcome (fixes + documented v1 limits) and the two piggybacked sticky type-fixes recorded; scoreboard corrected (prose F1.5 had also landed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
e187ab6d72 |
docs(agent): F3 cerrada · doctrina «The review surface» — split framework/app
Cierra los dos flecos de F3 tras la corrección del usuario (la review-card con diff era anclaje en dev-tools, sin sentido a nivel framework): - agent.md §7 gana «The review surface»: el FRAMEWORK posee los estados del ciclo expresados perceptualmente (reviewing/escalated con afordancias authorize/reject/cancel — Aura ya las da) + la semántica de «propuesto por otro actor» + los anuncios atribuidos; el APP posee el CONTENIDO de lo que se revisa, en su propio dominio (un documento enseña sus bloques propuestos, un calendario sus eventos pendientes). Una tarjeta-diff estilo git es legítima en una aplicación concreta; jamás canon. - §6 fatiga de aprobación enmendada en el mismo sentido (el cambio concreto en el dominio del app, nunca prosa). - PLAN: F3 CERRADA — review resuelta como doctrina sin componente nuevo; `allowedFamilies` del primer componente actuado reubicada a F2 (su hogar natural es el piloto). Siguiente: F4 por la vía del chat. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
2c175c5d95 |
feat(aura): fases 7–8 · README eidos + component:audit PASS — Aura completa (0–8)
Cierra el primer componente del eje agéntico de punta a punta (las 9 fases de la ruta). `component:audit` PASS. - README eidos con comparativa (5 refs adyacentes — thinking-indicators, CopilotKit activity, Vercel useChat.status, presencia colaborativa Figma/Docs, assistant-ui) reconociendo el paisaje escaso: nadie tiene una semántica perceptiva de la delegación con a11y (informe §8.3). Decisiones + Gaps con disposición. - Fixes del audit: opacity literal `0.5` → `var(--opacity-50)` (R-4.2); excepción R-1.5 documentada (único focusable = Cancel, que compone Button); `apg: 'none — …'` en el morfo (A-1.4). Gates: component:audit PASS · morfo:vocabulary limpio · eidos-lint aura 0 inválidos · recipe-contract verde. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
6be7195f0f |
feat(agent): eje agéntico — motor $agent (F1) + Aura + promoción uix.scene
Materializa el eje ortogonal agéntico del ecosistema: la 8ª familia semántica
`delegate` («¿quién actúa ahora?») deja de estar sin materializar. El agente es
OTRO ACTOR (LLM, macro, regla, workflow) que actúa por la MISMA API pública del
provider; la ruta de llamada no se bifurca, la concreción semántica depende del
actor.
F0 · Doctrina
- docs/architecture/agent.md — la doctrina permanente (capítulo comparativo de
referencias + bibliografía de seguridad, máquina estados=verbos, contrato de
participación, fila §0, a11y, amenazas).
- docs/process/{PLAN,TRIAGE,INFORME}-agent — plan de ejecución con todas las
decisiones firmadas (D-AG.1–11 + ⚖️1/2/3), triage de 4 revisores externos, e
informe autocontenido para revisión externa.
F1 · Motor ($libs/actor + arts/agent, 24º arte)
- EngineAgent puro (sin DOM/runes, portable a servidor): máquina D-AG.4
(suggest/review/auto · escalated con reason+timeout · returned outcome-tipado
· kill switch · autorización journaled aunque sea auto), tool-loop D-AG.5
(acts secuenciales, fallos→verbos, unknown-capability acotado, idempotencia
por callId, presupuestos acts/turns/wall-clock vía puerto de timers), techo de
autonomía por origen no confiable (F8b).
- ActiveAgent (sesión reactiva, contrato ActiveEngine) · protocolo v1 espejo
AG-UI (5 categorías + dirección tipada + reservas) · ScriptedAgentTransport
determinista (adapters/ fuera del barrel) · journal WAL + puerto de trazas
OTel · emisor sium→JSON Schema.
- Acuñación del actor (⚖️2/F6b): ActorToken opaco en $libs/actor (hoja bajo
orca/agent), registro privado WeakMap — los forjados resuelven a null;
costura `actor?` en TriggerOptions→SemanticSignal (runtime copia verbatim;
sema no resuelve). defineActiveAgent (service-factory app-level, timers del
core — cero setTimeout a pelo).
F3 · Aura — primer componente del eje (ruta 9 fases 0–6)
- El materializador canónico de `delegate` (reservado en scene §F6): morfo con
los eventos del ciclo (offer/escalate-untilAction/return + sustain-processing
stateBound) — PRIMER morfo del ecosistema que emite familia delegate; provider
soma que observa un puerto estructural (sin importar $agent — degradación
total); orb eidos = aurora $scene modulada por estado (§F6) con fallback CSS
= render de reduced-motion; live region única atribuida (WCAG 4.1.3); cancel
compone Button (asChild). Demo v2 sobre el MOTOR REAL con compuertas
deterministas. Orb-size derivado de la primitiva Avatar (32/40/48).
D4 · Promoción uix.scene (Aura llegó)
- defineEngineScene + superficie ActiveUix/ActiveEidos/contracts; el orb prefiere
el motor compartido (presupuesto de escenas global) con fallback por-superficie.
Gates: arts/agent 18/18 · aura 6/6 · arts:check 24 · eidos battery + recipe
30/30 + eidos-lint aura 0 inválidos · morfo:vocabulary + esquemas · smoke aura
PASS · verificado en navegador (ciclo completo + estampa delegate en DOM +
aurora WebGL pintando). Los tokens de recipe base.ts + CSS generado ya entraron
en HEAD vía una sesión concurrente.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
955c6a18b3 |
docs(blocks): registro F1.5 prose · 5/8 F1
|
3 months ago |
|
|
11ba4b906b |
docs(blocks): registro · review sticky + fix writing-mode · F1.1 CERRADO
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
c6964496b4 |
docs(blocks): registro F1.1 sticky · 4/8 F1 hechas
Registra sticky (
|
3 months ago |
|
|
9124570e69 |
uix(callout): F1.4 · admonicion inline (morfo+eidos) al suelo del dossier
Tercera pieza F1 del plan blocks (alcance E-2, dossier §P3). Banner = tira de anuncio de pagina; Callout = el aside del documento (split deliberado). - morfo: 4 partes (provider role=note / icon / title / content) + texts con TITULOS DEFAULT LOCALIZADOS por intent (note/tip/warning/caution — patron GitHub de label visible: la semantica nunca viaja solo en color/icono); 0 eventos justificados (dismissible DIFERIDO a pasada soma+sema por la regla de admision — dismiss ES un evento real) - el hueco IMPORTANT resuelto con el modelo Radix, sin 5o enum: `intent` (neutral|affirm|risk|threat — mapea NOTE/TIP/WARNING/CAUTION) + `color` override SOLO bajo neutral (doctrina §4: intent evaluativo gana); IMPORTANT = intent neutral + color + titulo propio (preset en la demo) - pintura sobre la maquinaria C6/THM-2 (patron badge): forwarders por color + slots `_palette-track/text/solid` → el generador emite la cascada de 8 roles Y el forward presence-guarded al shared layer — 33 escalas y colores custom con CERO CSS extra - a11y: role=note NOMBRADO via aria-labelledby → Title SOLO mientras esta montado (registro reactivo por context local); escalacion tipada role=status|alert|none (el role=alert estatico de shadcn = bug de referencia que NO copiamos); Title NO es heading (protege el outline que escaneara anchor-nav); icono decorativo = mapa doctrinal IntentIcon - fix cazado en navegador: el `+=` del registro leia el estado dentro del tracking del $effect del hijo → effect_update_depth silencioso (contador a -997, attr nunca estampado) — untrack() en register/cleanup; leccion registrada en memoria (incidente 2 de la clase) - recipe: grid con acento logico border-inline-start (RTL-correcto), nesting tolerado (Docusaurus); demo v2 9 tabs con preset IMPORTANT + PalettePicker showIntent=false; README completo Verificacion: component:audit PASS 0E/0W · eidos-lint 8 morfo-backed/0 invalid · guards 37/37 · svelte-check 76E/51W = baseline exacto · navegador: «Nota»/«Atencion» localizados · risk=ambar hue 45-60 · IMPORTANT=plum hue 326 resuelto por shared layer · labelledby=titleId · cero errores de consola. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
bf940c9a4a |
uix(result): F1.3 · estado terminal de flujo/pagina (morfo+eidos) al suelo del dossier
Segunda pieza F1 del plan blocks (alcance E-2 = suelo de paridad, dossier
§P3). Comparte el esqueleto de empty-state a proposito: EmptyState describe
AUSENCIA de datos, Result reporta un RESULTADO — un lenguaje de layout, dos
contratos (duplicacion consciente registrada en el README; revisable al 3er
consumidor).
- morfo: 6 partes display (provider/media/title/description/actions/extra;
extra SIN archetype — parte genuinamente propia), scope ['eidos'],
0 eventos justificados (el resultado ya OCURRIO antes de renderizar)
- status: enum de 7 = paridad AntD CON `warning` (decision firme del
dossier) y HTTP renombrados semanticos (forbidden/not-found/server-error,
nunca '404' stringly); default 'info'; data-status = attr eidos-only
- media default: compone el mapa doctrinal IntentIcon (success→fulfill ·
error→threat · warning→risk) + Info de catalogo; HTTP = codigo mono
grande NEUTRO aria-hidden (situaciones, no fallos — AntD jamas pinta 404
de rojo); children reemplazan el default entero (context local eidos-only
con getter reactivo)
- Title default h2 (vs h3 de empty-state — Result suele SER la pagina);
Actions con label→role=group; Extra alineado a inicio (detalle que lee)
- recipe: glifo display 3× xl bundle; tokens publicos
gap/actions-gap/measures + {status}-color como forwarders de rol
retintables; sin eje size (paridad AntD, gap diferido)
- demo v2 9 tabs con copy por status + entrada nav (Status) + README
(Comparativa · Decisiones · Passive justification · Gaps con disposicion)
Verificacion: component:audit PASS 0E/0W · eidos-lint 6 morfo-backed + 5
eidos-only sancionados · recipe/api/visual-attrs 37/37 · morfo:check verde
(los 7 fallos listados son deuda foranea preexistente) · svelte-check
76E/51W = baseline exacto · navegador: success=fulfill verde 84px ·
error=threat rojo · not-found=«404» mono neutro, copy conmutando, cero
errores de consola.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
f19e91b985 |
uix(empty-state): F1.2 · componente base display (morfo+eidos) al suelo del dossier
Primera pieza F1 del plan blocks (PLAN-blocks.md; alcance E-2 = suelo de paridad, dossier §P3). Ruta de 9 fases completa: - morfo: 5 partes display (provider/media/title/description/actions), scope ['eidos'], 0 eventos con justificacion pasiva (patron renderEmptyState de las refs headless), Title role:'heading', texts.label - langs: components.empty-state.label (es/en) — el indice tambien recoge la retirada foranea del import de words (inseparable por staging de archivo; coherente con la migracion palabras ya enviada; words.ts sigue en su arbol) - eidos: compound EmptyState + Media(kind icon|media, placa 2x glifo) + Title(level 2-6, default h3 — modelo Atlaskit, tamano visual desacoplado) + Description(measure 45ch) + Actions(label -> role=group +aria-label, buttonGroupLabel); recipe sobre el bundle --size-* (titulo un paso discreto arriba; sm=in-collection, lg=hero); tokens publicos minimos (gap/actions-gap/media-bg/media-fg/media-radius/description-measure) - demo v2 9 tabs (harness: SystemAxes/MotionPanel/SemaPanel; snippet con paridad; escena in-collection via Card) + entrada nav (grupo Status) - README: Baseline · Comparativa (shadcn/Chakra/Atlaskit/AntD/Polaris) · Decisiones · Passive justification · Gaps con disposicion Verificacion: component:audit PASS · eidos-lint 5 morfo-backed + 4 eidos-only sancionados · morfo:check + morfo:vocabulary verdes · recipe-css-contract/api-contract/visual-attrs 37/37 · svelte-check 76E/51W = baseline exacto (cero regresion) · navegador claro Y oscuro por estilos computados (titulo 18->24px, placa 40->64px, chips vivos, cero errores de consola). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
647db398ee |
blocks(plan): enmiendas E-1..E-5 firmadas — F1=8 (+nav-tree), F2=14, paridad=v1
Firmas del usuario (2026-07-21) tras el dossier de referencia: - E-1: componente canonico nav-tree (F1.8, data-driven — precedente Menubar) resuelve la colision sidebar-app vs arbol-docs/B-5; fiche nueva con fase 0 obligada contra nuestro tree-view; docs-shell compone NavTree. - E-2: suelo de paridad del dossier = alcance v1 (regla en intro F1). - E-3: F2 pasa de 10 a 14 (banner · team · contact · content-section); fiches nuevas + galeria actualizada (verificada en navegador: 28 badges). - E-4: distribucion registry/llms.txt/MCP → iniciativa propia (next-features §9; ese archivo sigue fuera del commit por diff foraneo). - E-5: ⌘K = listener app-land documentado (nota en F4.1). Doctrina: promotion path de architecture/blocks.md actualizado a 8. Verificacion: docs:check 0/0 (512). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
aff42fee0f |
docs(blocks): dossier de referencia — 6 pistas, suelos de paridad + enmiendas E-1..E-5
Encargo del usuario (2026-07-21): estudio de referencia para "estar al menos a la par y cuanto menos superarlo". 6 agentes de investigación web en paralelo sobre fuentes oficiales vivas (varias verificadas a nivel de código fuente: sidebar.tsx del registry shadcn, styles.js de tailwind-typography, sources de Mantine/Starlight/Docusaurus/Ark). - docs/process/RESEARCH-blocks-references.md — el dossier: P1 catálogos de marketing (conteos+variantes) · P2 aplicación (ProTable/ProLayout, dashboard-01, Tremor 303, Clerk/Supabase) · P3 F1 ligeros (APIs a nivel de prop; scroll-state(stuck) como vocabulario de plataforma; role=alert de shadcn = bug a no copiar) · P4 prose+sidebar (not-prose/:where()+donut; shadcn 23 partes) · P5 docs shells (Fumadocs benchmark; colision F1.7 vs nav-tree/B-5; ruta svelte2tsx para props TS) · P6 ecosistema Svelte (shadcn-svelte 58 blocks solo-app; hueco "sistema integrado" VACIO) + sintesis: 5 conclusiones, enmiendas E-1..E-5 (presentadas, firmas pendientes), 10 angulos de superacion. - PLAN-blocks.md — regla nueva §4 (toda fase 0 contrasta contra el dossier), puntero de colision en F1.7, registro. Verificacion: docs:check 0/0 (512 docs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
0697596046 |
blocks(tier): F0 · infraestructura del tier (doctrina + $blocks + guard + galería)
Tier nuevo aprobado 2026-07-21 (D-BLK firmadas; D-BLK.1 enmendada: vive en src/uix/blocks/). Plan de ejecución completo en docs/process/PLAN-blocks.md. - docs/architecture/blocks.md — doctrina E1 (regla de admisión canon-vs-block, contrato B-1..B-11, promotion path); espejo de packs.md - alias $blocks → src/uix/blocks (vite + svelte.config + tabla CLAUDE.md) - src/uix/blocks/README.md — mapa del tier + template de README de block (B-9) - scripts/blocks-check.ts + npm run blocks:check — guard mecánico del contrato B con self-test de detectores (fixtures inline; 5532 archivos de canon/arts/libs/packs escaneados para dirección de imports) - web/routes/blocks/ — galería índice del catálogo planificado + layout de bootstrap propio (+layout@.svelte, espejo mínimo del de /uix sin packs sema) Verificación: docs:check 0/0 (511) · svelte-check 76E/51W = baseline exacto · blocks:check verde · galería comprobada en navegador (a11y-tree + estilos computados en light y dark; captura de píxel bloqueada por panel suspendido). Fuera del commit: fila E1 en docs/README.md y §8 en docs/next-features.md (diff foráneo de otra sesión sin separar — ver registro del plan). Co-Authored-By: Claude Fable 5 <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 |
|
|
44a016538c |
docs(color): plan verificado de Contraste Stage 2 (generador by-construction)
Plan de ejecucion para sesion aparte, VERIFICADO adversarialmente (workflow de
7 agentes contra codigo y docs) antes de escribirse — corrige 5 afirmaciones
de la respuesta que lo origino:
- "por delante de Radix (hex sRGB)": FALSO — el propio RFC §14 registra Radix
con APCA y P3 ("Radix style"); la posicion del repo es "on par"; nuestra
paleta enviada es sRGB-equivalente (oklch = siblings de hex).
- "el solver satisface la tabla §40": el UNICO floor duro ratificado es
text-strong·12 ≥ 4.5; text·11 quedo tier blando (decision D1); solid·9 esta
anclado exacto a la semilla; el script de auditoria aun codifica la tabla
PRE-veredicto (consumirla contradiria los veredictos → F0 = tabla como datos).
- "los tipos ya existen en parte": ColorScaleSeed/Source son solo prosa del
RFC; config-seeds imposibles hoy (solo applyColorScheme roles-only +
generatePalette full-33 como bancos).
- "792 hex en base.ts": exacto en total pero en DOS ficheros (288 base.ts +
504 color-scales.ts); 4 escalas ya autoradas desde semillas offline.
- "nada downstream cambia": nombres si (contract.ts), pero hay pins de VALOR
disenados para dispararse (hex literales, flip-set de polaridad,
generated/base.css byte-exacto) + persistencia rechaza version ≠ 1.
El plan: F0 tabla ratificada como datos + harness sobre output generado ·
F1 solver (biseccion contra sRGB gamut-mapeado, salida hex, desviacion minima
del donante) · F2 validacion en bancos de semilla + navegador · F3 (gated)
migracion base→seeds con re-ratificacion de pins y pase visual. 6 decisiones
de usuario ABIERTAS al frente (target text·11, semantica base+ΔE, modo
on-solid, colocacion/alcance, tipos Fase 0, normalizacion de la tabla).
next-features §1 enlaza el plan. (El hunk ajeno de chat-list en next-features
queda fuera del commit — danza clean-desde-HEAD.)
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 |
|
|
ae2a27208e |
docs(color): actualiza el handoff de "abrir la jaula del color" con el estado real
Reescribe docs/process/open-color-cage-2026-07.md tras los 7 commits de esta sesión: familias hechas (field/calendar/time/list-grid/lists + link/mark/ button-group/highlight), los 5 PATRONES de migración (A eidos-wrapper · B soma-routed · C mini-recipe month-grid · D tinta de contenido · E delegante), las 6 TRAMPAS cazadas (forma declarations[] para `parts`, recipe-exists fiable/no duplicar clave, declarar solo slots consumidos, `--x:inherit`, cross-portal, prefijo accent/accent-soft), y la COLA de ~11 standalones heterogéneos con su patrón + trampa cada uno (incl. color-picker/float-panel intentados y revertidos por matiz cross-portal/default). Operativa corregida: el total de `check` NO es baseline estable (76↔94 por caché svelte-check) → verificar CERO en tocados; índice git compartido + exclusiones (chat-track en soma/virtual-list, palabras, alpha). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
149c0fef6b |
uix(color): abre `color` al sistema completo (role·intent·paleta·valor) en la familia interactiva + rail segmentado de radio-group
Motor (Fase 0): tipo abierto `ComponentColorProp`, helper `resolveComponentColor`,
derivación custom compartida `[data-color-custom]` (color-mix desde `--color-custom`)
+ forward extendido a `[data-{c}][data-color-custom]`; guard recipe-css-contract
actualizado.
Eidos-wrapper (14): radio-group, checkbox, stepper, toggle-group, select, badge,
editable, file-upload, tag-group, tags-input, surface, avatar, card. Patrón:
tipo -> ComponentColorProp, wrapper -> resolveComponentColor (estampa
data-color/data-color-custom/style), demo con picker + input custom. Incluye el
fix del bug de escalas de card (una escala caía al path custom y pintaba el CSS
named color en vez de la escala de paleta).
Soma-routed (button/switch/toggle): patrón morfo+soma limpio — el provider omite
data-color y emite data-color-custom para el valor crudo, con el intent evaluativo
ganando y suprimiendo el custom. Ensancha los tipos de color de la capa soma.
Arreglados 2 bugs del runtime del framework:
- html-presence con `v.literal('')` (valor vacío = falsy) nunca emitía el attr ->
`v.propRef('colorCustom')` (como data-disabled).
- el custom pisaba al intent evaluativo -> el prop `colorCustom` se anula con
intent no-neutral.
Bundle: incluye el rail segmentado de radio-group (variant=segmented, indicador
deslizante compartido soma-mide/eidos-pinta: measured-indicator + sliding-indicator)
de la misma sesión, intercalado en los ficheros de radio-group.
Handoff para continuar: docs/process/open-color-cage-2026-07.md (Fase 3/4/5 pendientes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
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 |
|
|
90b66ea138 |
docs(proof-of-human): "Adding a new challenge" recipe in the orchestrator README
8-step path distilled from how RotateAlign/Clock were actually built (morfo -> langs -> soma provider vs the ChallengeHost -> telemetry block -> sema pack -> eidos skin -> anti-bot tests -> demo+gates) + the non-negotiable invariants (challenge never owns the verdict, no network inside, random per instance). Also refreshed the stale bits: the orchestrator README now names both shipped challenge engines and documents the rotation telemetry block alongside pathAdherence; eidos README points to the recipe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
8b06f6bc70 |
docs(proof-of-human): close out the component - READMEs + challenge-aware demo
- soma rotate-align README (was missing): anti-bot core, parts, props incl. targetSnapDeg, keyboard, rotation telemetry, sema events, compass+clock skins. - eidos README links all three challenge morfos/READMEs. - demo: the Live snippet, API tab, morfo tab and Services tab now follow the SELECTED challenge (RotateAlign/Clock knob table added; the two challenge morfos share handle-* names so tables swap instead of merging). - prettier normalization of the compass rotate-align.svelte (pre-existing warn). - handoff marked COMPONENT COMPLETE; only v2 roadmap + foreign text-blur blocker remain. Gates: component:audit 144/144 PASS, svelte-check clean on touched files, prettier clean, demo tabs verified live in Chrome. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
cdb3db7095 |
feat(proof-of-human): Clock - cuckoo skin over the RotateAlign engine
- soma: targetSnapDeg knob quantizes the random target bearing (the clock needs hour-aligned targets: a free bearing misses the marked hour by up to 15deg vs the 8deg tolerance ~47% of the time); default off, unit-tested. - eidos: clock.svelte + clock.css - face (11 ticks, numbers at 3/6/9, accent target-hour badge), cuckoo house at 12 (roof, doorway, swinging doors, sill), FRONT-facing messenger pigeon with the letter, hour hand. Verified-state dispatch (doors swing -> peek -> hold -> fly) as bespoke content choreography, every @keyframes annotated functional: per R-4.5; sill occlusion via clipPath; outer/inner SVG transform split; fulfill recolor on verdict; reduced-motion = static doors open + peek. - exported ProofOfHuman.Clock + ClockProps; eidos README challenge list. - demo: Clock chip reusing the rotate knobs; clock idle instruction; the success card waits 3.3s for the cuckoo; corner stage-pigeon suppressed in verifying AND verified for the clock (the cuckoo IS the pigeon). Includes prettier normalization of the demo page. - handoff doc rewritten: clock shipped; filming/throttling gotchas recorded. Verified: 81/81 tests (poh scopes), component:audit 144/144 PASS, svelte-check clean on touched files, end-to-end solves in real Chrome (light + dark). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |
|
|
724ab51ed8 |
docs(proof-of-human): rewrite handoff — RotateAlign shipped, clock skin is next
Full self-contained handoff for a fresh session: architecture, both shipped challenges (PathTrace + RotateAlign), the clock-skin spec (cuckoo + front-facing pigeon, from the iterated mockup), the SVG transform-override gotcha, the concurrency warning (another session on the same branch), verification-env gotchas, and the commit list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
139cc2b671 |
docs(proof-of-human): mark end-to-end verification + pointer test done
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
e1b5dec844 |
docs(proof-of-human): mark component:audit done in continuation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
1f7018b910 |
feat(proof-of-human): re-architect to orchestrator + pluggable challenge (PathTrace)
Split the anti-bot component into a purpose- and interaction-agnostic orchestrator that owns the lifecycle stages + verdict, and a pluggable challenge that runs the interaction and reports up via ProofOfHumanChallengeHost. The primary challenge, PathTrace, has the user follow a random curved path by pointer OR arrow keys. This fixes the drag version's two real defects: - keyboard bypass: the accessible path now yields genuine, non-replayable behavioural signal (pathAdherence over a per-instance random curve); a two-key script can't solve it. - over-coupled abstraction: interaction + animation are decoupled from the component identity (per-stage consumer slots; challenge is pluggable). Layers: - morfo: proof-of-human slimmed to orchestrator (Provider + 3 commits); new path-trace morfo (Provider + Track + Token; handle-*). - soma: orchestrator + ProofOfHumanChallengeHost; new path-trace (seeded Catmull-Rom curve, corridor-gated monotonic progress, adherence telemetry, pointer + arrow-key input). Old drag Token/Target wrappers retired. - sema: verdict commits (proof-of-human) + trace handle earcons (path-trace). - eidos: orchestrator + PathTrace that paints the curve/corridor/trail. - demo rebuilt: carrier-pigeon skin over the per-stage slots. Auto-focuses the token when the challenge is armed so the arrow keys start the trace immediately (gated on the disabled->enabled transition). Verified: 32/32 tests (incl. arrow trace completes with real signal + two-key script can't solve, in real chromium) + visual in real Chrome (curve, failed, verified; scorer rejects instant traces). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
e0d82a9044 |
docs(theming): close F4-D queue — anti-CLS shipped, container-query dogfood declined
Record the last two F4-D optionals as resolved: (1) anti-CLS metric fallbacks
shipped (
|
3 months ago |
|
|
d8b984bb5a |
test(motion): pin firma-vs-stagger cascade precedence (F4-D motion decision)
Workflow analysis (state map + 4 dispositions + adversarial stress) resolved the
direction dispute: the stagger/coordinated preset winning the (0,3,0) tie over
the generic commit-affirm firma is the DOCTRINE-CORRECT outcome (RFC B.2 — the
coordinated owns the visual axis, the generic firma is silenced). The earlier
"flip to firma" reading was backwards; that flip contradicts B.2 and its
channels:[] safety net is dormant; @layer sinks as a front-line fix (a semantic
ownership question answered with a global cascade tool + a reduced-motion
!important-inversion a11y risk).
No shipped component shows a defect (dropdown select feedback is sound+haptic
only; the item is fading out at select). The only real issue: the correct
outcome rides on an ACCIDENTAL source-order tiebreak (the explicit neutralization
was retired), undocumented + untested. Execute the proportionate fix:
- Regression test (motion.test.ts) pinning the (0,3,0)==(0,3,0) tie + the
signatures-before-presets emission order + the absence of a (0,4,0)
[data-event^='commit-select'] signature — a HAZARD MONITOR, so a generator
reorder or a widened signature fails loudly, not silently.
- Load-bearing comment at the render-css emission order.
- Doctrine note in motion.md: a known-fragile accident, not a contract; the
structural endpoint ("una firma por evento") + @layer are deferred/tracked.
Zero behavior change; base.css unchanged. Tests 28/28; npm run check src/ = 9
baseline, 0 new.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
a52f80ceab |
refactor(eidos): prune the depth `scrim` cue (F4-D scrim decision)
`--depth-{plane}-scrim` was declared (DepthPlane field, DEPTH_CUES, validation,
applyDepth) but never seeded, emitted or read — a write-surface with no reader
(applyDepth({p:{scrim}}) silently no-op'd; never present in generated base.css).
Analyzed via workflow (state map + 4 dispositions + adversarial stress). The
four collapse to prune vs promote-to-z-parity: wire-and-unify SINKS (contradicts
the test-locked z open-cage precedent + the MD3/Radix/Vaul consensus that the
veil is component-owned, not an elevation cue), relocate flips its verb to
retain, and complete-to-z-parity would mint a third alias of the --color-overlay
backdrop role. Unlike z (kept as an exposed open cage — it varies per plane and
is unpaintable-forever), scrim has no per-plane variance and no value to seed.
Prune: remove scrim from DepthPlane / DEPTH_CUES / validation / emitter +
applyDepth doc. Amend rfc-depth.md (token contract + Phase-4 note + atmosphere
rows), reference.md cue list, changelog, pendiente. The modal veil stays where
MD3/Radix/Vaul + the standing decision put it: --{component}-overlay-* tinted by
--color-overlay.
Zero observable change (never emitted). Tests 71/71; npm run check src/ = 9
baseline, 0 new; base.css unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
6c55e092b5 |
docs(theming): F4-D adjudications — register deliberate limits + KEEP verdicts
Close the no-decision F4-D GAP items after the professional (reference-grounded
+ adversarial) analysis. Constant finding: near-zero real debt — most items are
deliberate reserved axes or stale audit premises.
- layer-4 / depth-z / container-queries -> KEEP (documented reserved axes; MD3/
Primer/Radix/Tailwind/DTCG precedent). The audit's "layer 4 sin consumidores"
is stale — it's consumed by banner/toast/avatar/editable/words + the THM-2
fallback. Fix the one real defect: the false comment at render-css.ts depth
block claiming the floating-wrapper consumes --depth-{plane}-z (it reads the
computed z from --{component}-content-z / --z-index-*; depth-z is an unwired
reserved read-surface).
- Register the 4 deliberate limits (LIM-SND/ICON/TOUCH/RECIPES) in the gap-log.
- assets-variables shipped in 7d4f9500; scrim + motion-direction remain design
decisions (documented, not executed to avoid pinning the wrong call).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
58c798b195 |
feat(eidos): F4-D foundation — themed selection, caret, scrollbars
Add a "Foundation globals" section to eidos/archetypes.css (document-wide, not archetype-keyed) closing the two zero-coverage systemic gaps + the scrollbar item from the F4-D GAP menu: - ::selection — translucent accent wash (background only, text keeps its colour so it stays legible in light and dark) - caret-color — accent identity on input/textarea/contenteditable, in :where() so a field recipe can override (invalid -> threat) - native scrollbars — themed + thin via the STANDARD properties on :root (Firefox + Chromium >= 121). Heavy ::-webkit-scrollbar deliberately skipped: ScrollArea draws overlay bars and hides the native one inline (scrollbar-width: none on its viewport, which wins). Thumb colour matches ScrollArea's thumb. Cursors were already archetype-wired (trigger/close/action/item/option = pointer, thumb = grab/grabbing, disabled = not-allowed, text zones = native input + explicit cursor:text in date-field/editable) — no change needed. All values read role tokens (--color-primary-solid, --color-content-muted), lowest specificity so any component recipe still wins. Verified live (tags-input demo, base-dark): input caret = --color-primary-solid exactly; scrollbar-width thin applied; ::selection rule present + token resolves; docs-harness :root override wins (app > foundation). npm run check src/ = 9-error baseline, 0 new. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
1c7b4e7f51 |
docs(process): continuation handoff — session close state (THM-2 + THM-4 done)
Add a close-of-session state block at the top: what shipped this session (media-player Batch-4, THM-2 full rollout + avatar + closure, THM-4 5-hotspot adjudication) with commit refs, the real-component verification method, and the remaining §9 / side queue (F4-D GAP scoping, motion cascade-vs-signatures). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
34d1d46bdb |
docs(theming): close THM-4 — 5 hotspots adjudicated, only 1 real debt item
Adjudicate color-picker (29), avatar (28), time-range-picker (26): all 0 debt — composition (de-dialoged pickers compose Popover; avatar's per-role color now lives in the shared layer after the THM-2 migration; sliders/toggle composed), behavioral (sema last-action, format, endpoint, loading status), and per-file-lint blind spots (recipe-token variant rules). Across the 5 biggest hotspots (156 unused) the only real debt was stepper's dual-representation API, now consolidated. Confirms the doctrine: the unused column stays informational, no machine rule warranted. THM-4 closed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
75fa94005b |
refactor(stepper): THM-4 — consolidate step state on data-state, drop boolean aliases
Every step part (item/trigger/indicator/separator) declared BOTH `data-state` (enum) AND `data-current`/`data-complete`/`data-incomplete` (booleans) — the same state twice, used inconsistently (enum on trigger/indicator, boolean on separator). Consolidate on the canonical `data-state`: - morfo: prune the 12 boolean declarations. - provider: drop the current/complete/incomplete sources (item/indicator/separator) + the trigger's explicit boolean writes + the now-unused isComplete/isIncomplete getters (isCurrent stays — it drives aria-current/tabindex). - separator.css: [data-complete] -> [data-state='complete']. - README + provider test updated to the single form. Verified with the real component (Next advances → complete/current/ incomplete via data-state, boolean attrs gone, complete separator still paints). stepper unused 30->18; provider test + contracts green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
d9c3d03f7d |
docs(theming): THM-4 dossier — media-player + stepper adjudicated, low debt rate
Burn the two biggest THM-4 hotspots. media-player (47 unused): 0 real debt — all composition artifacts (composed Buttons/Sliders), behavioral state mirrors of the media element, default-state enum values, or documented-v2 roadmap surfaces (settings-button, custom captions). stepper (30 unused): mostly legit + one API redundancy — every step part declares both data-state (enum) and data-current/complete/incomplete (booleans), used inconsistently (enum on trigger/indicator, boolean on separator). Consolidating is a public-API design decision, not a prune. Emerging read (2 batches): hotspots are mostly legitimate; genuine declared-for-nothing debt is rare, confirming the doctrine — the debt rate does not justify a machine rule. THM-4 is mostly documented adjudication. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
3 months ago |
|
|
0a5aa387b9 |
docs(theming): close THM-2 rollout — Avatar.Badge out of the scale palette by design
Record the rollout as shipped ( |
3 months ago |
|
|
cc7e9a162a |
feat(theming): THM-2 - avatar root palette to the shared layer + 33 scales
The avatar body's per-instance palette now resolves through the shared
per-instance layer (size collapse, behavior-preserving for roles, adds
the 33 scales):
- avatar.svelte: isCanonicalColor recognizes palette scales, so a scale
name routes to data-color instead of the custom CSS-color path ('teal'
is also a CSS color name, so it used to fall to --avatar-color-custom).
- recipes/base.ts: _palette-{solid,surface,contrast,text,border} (host
fallback only - the avatar always emits a canonical data-color, so the
presence-guarded forward routes roles + scales) + _bg/_fg/_border move
from a variant x color matrix to variant-only readers of
--_avatar-palette-*. The matrix helper + intermediates stay (the badge
still uses them; 0 orphans). avatar.css unchanged.
- demo: scale picker.
Verified end-to-end with the REAL component (drive the demo color control
-> the avatar emits data-color -> the shared layer resolves the exact
scale): affirm+soft behavior-preserving, teal+soft = scale-teal-a2
(translucent surface), teal+solid = scale-teal-9.
The Avatar.Badge sub-part (aria-hidden decorative chip with its own color
+ default) stays on its role palette + custom-CSS: migrating it needs a
per-part forward + pruning ~36 orphaned intermediates, disproportionate
for a decoration whose arbitrary-color case already works. Documented for
a decision.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
05f0031cad |
feat(theming+soma): media-player Batch-4 (SOM-4) + THM-2 shared-palette rollout to 14 surfaces
media-player Batch-4 (closes SOM-4): the 10 sub-parts migrate to
renderProps() (35 attr-dup hits -> 0). Register the sources the morfo
reads (label propRef, on stateRef, direction/type) so renderProps emits
type/role/aria-*/data-state; keep only contract-only soma-owned attrs.
Pin the soma-attr-audit ceiling to 0.
THM-2 rollout - collapse each per-component palette cascade into the
shared per-instance layer (size win, behavior-preserving for roles,
adds the 33 scales):
- Rename palette tokens to canonical _palette-{slot} + route via the
presence-guarded forward: tags-input, stepper, badge, card, select
(parts-aware trigger+content), file-upload, editable, tag-group.
- Extend the shared vocabulary with alpha surface(a2)/surface-hover(a3)
slots (soft variant of variant-aware recipes) and hover(4) - the
latter also fixes Toggle's latent scale-hover.
- tag-group: variant-aware migration (solid/outline -> canonical,
soft -> alpha surface; merge outline-fg into text, prune orphans).
- toggle-group: replace the 4-role cross-recipe composition with a
hand-authored presence-scoped route to the shared layer (the
composition DSL can't presence-scope); covers roles + 33 scales.
- Widen color to ComponentColor (roles + 33 scales) where arbitrary
color is meaningful: badge, tag-group, tags-input, toggle-group, and
add the scale picker to their demos. Semantic controls stay restricted.
Verified end-to-end with the REAL components (drive the demo color
control -> the component emits data-color -> the shared layer resolves
the exact scale). Gates: recipe-css-contract/config/contracts/
soma-attr-audit/media-player suites green; 0 orphan tokens.
avatar remains for a focused pass: its variant-resolved matrix and badge
sub-part don't fit a mechanical migration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
3 months ago |
|
|
6da8466f68 |
docs(process): handoff — media-player Batch-4 scopeado como arranque de la próxima sesión
Estado: F0·F1·F2·F3·F4-C·SEM-4·THM-2 ✅ commiteadas/pusheadas. El bloque
ARRANQUE reescrito: media-player Batch-4 (cierra SOM-4) con el diagnóstico
completo (10 sub-partes hardcodean attrs que el morfo declara; migración
renderProps parte-a-parte; plantilla carousel/table; cierre con ceiling +
navegador) para ejecutar en frío sin re-scopear. Cola posterior: rollout
THM-2 (12) · hotspots THM-4 · F4-D GAP (scoping) · cascada-vs-firmas.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
0e75890f60 |
feat(theming): THM-2 — paleta universal por instancia (capa compartida + forward), 5/17 superficies
El mecanismo genérico ya existía (universalPaletteDecls auto-completaba las 33
escalas para recetas con tokens palette-*), pero con PREFIJO de componente
(--button-palette-*) — cada superficie pagaba ~21 KB, por eso solo button/toggle
lo tenían (rollout a las 17 = ~+315 KB). Colapso al modelo compartido:
- renderSharedPaletteLayer: UNA cascada component-agnóstica
[data-color='{role|scale}'] { --palette-{slot}: … } — 8 roles + 33 escalas,
emitida una vez (~19 KB). Roles 1:1 a --color-{role}-*; escalas con el step
map + on-solid contrast, valor idéntico a la cascada per-component retirada.
- renderRecipePaletteForward: toda receta con palette-* (público) o _palette-*
(privado) recibe [data-{c}][data-color] { --{c}-palette-{slot}:
var(--palette-{slot}, host-default) }. La guarda de PRESENCIA es la seguridad
de anidamiento — un hijo sin color dentro de un ancestro coloreado matchea
solo [data-{c}] (su default) y nunca lee el --palette-* heredado.
- Generalización a la paleta PRIVADA: checkbox/radio-group/switch ganan las 33
escalas con CERO cambio de CSS (ya consumen --_{c}-palette-*).
- universalPaletteDecls eliminado; 2 guards migrados al modelo compartido
(recipe-css-contract "shared layer + forward"; active-eidos-config contrast
en la capa compartida).
DONE (per-instance): button, toggle, checkbox, radio-group, switch (5/17).
Rollout de las 12 restantes en docs/process/continue-cleanroom-fixes §THM-2.
Doctrina en theming/reference §25 ("The per-instance palette layer").
Verificado en vivo (button + checkbox): color="teal" → escala teal exacta ·
affirm → rol exacto · primary → primary · nested-nocolor en ancestro teal →
primary (sin contaminación). Peso 345K → 329K. Gates: matriz 141/141 ·
eidos 314 · recipe-css-contract + active-eidos-config 98/98 · contracts 38/38.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
63b371d5ce |
feat(packs+text): incorporate the animation collection — Ambient pack + text-effects family + docs
Two streams, split by what the animation touches:
STREAM A — decorative backgrounds → the pack tier
- arts/scene: a consolidated scene runtime ($scene) that owns, once, the
citizenship every ad-hoc background reinvented or skipped (frame loop,
off-view pause, DPR cap, mandatory reduced-motion, WebGL context
loss/restore, scene budget, teardown). SceneDom port (adom satisfies it),
webgl/webgl2/canvas2d drivers + a custom-pipeline extension (vertexShader +
draw + glContext.depth/dprCap) for real geometry (beam, particles, dither,
grid, eter, pixel-blast, hyperspeed). 32 effects as shared resources.
- src/packs/ambient: the first pack — <Ambient effect="…"> mounts a registered
effect; the P contract (P-1..P-6) guarded by scripts/packs-check.ts; colors
are token-aware (P-4). One-way dependency, removable-by-construction.
- resolveToken extended to semantic color slots (--color-{role}-{slot}) so
consumers resolve theme tokens to concrete colors (the P-4 half).
STREAM B — animations over real text → canon
- Six components (count-up + text-{gradient,circular,blur,focus,scramble}):
each a morfo + eidos recipe (where there's styling) + demo. CountUp is a
service component (counts through uix.format.numbers). The five Text* are
passive decoratives. Upgrades over the seeds: SR hardening (real text
visually-hidden + aria-hidden decoration), a11y fix (no fake role=button),
measurement discipline (cached rects via dom.measure, no reflow storm),
reduced-motion, ecosystem citizenship (eidos.dom/timers, no raw platform).
- MorfoElement gains 'p'.
DOCS
- docs/architecture/packs.md (pack tier, admission rule, P contract, Aura
promotion path); docs/decisions/design-text-effects.md (the family design
record) + indexed in decisions.md / README.md; glossary entries
(scene/Ambient/Aura/text effects); scene README custom-pipeline + authoring
bridge; motion-guide content-effects note; strata tables acknowledge packs.
Gates: component:audit 141/0/0 · docs:check 0/0 · scene tests 9/9 · packs:check
0/36 · check 0 own errors. Verified in browser (32 effects mount+compile;
6 text components SSR+hydrate, CountUp re-formats by locale live, TextGradient
resolves token stops to OKLCH via var()).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3 months ago |
|
|
082b66232a |
docs(process): SEM-4 COMPLETO — D.6 ya estaba vivo (registro reconciliado) + D.2 descartado
- book-deviations: los caveats de D.6 (emisión dormida + branch-vs-item) marcados
RESUELTOS con verificación en vivo 2026-07-12 (los 6 providers emiten; packs con
onBranch/onRow casan con los targets reales) — el párrafo stale fue citado por la
clean-room como evidencia de dormancia: lección anotada (el registro se actualiza
en el mismo pase que el cableado). D.11: párrafo de pickers actualizado al cierre
real (delegado closeWith→dismissWith,
|
3 months ago |
|
|
b55ca6eecb |
fix(cleanroom): F2+F3+F4-C+SEM-4s1 — lote mecánico, censos con guard, corpus documental y el close polimórfico de los pickers VIVO
F2 — lote mecánico (13 ítems): - DEP-2 ogl eliminado (0 imports) · DEP-1 clsx inlineado como toClassString propio + suite de contrato (props.test.ts; soma.md §12 cerrado). - THM-7: los 5 selectores manuales de sema.md reescritos con semaSelector (los ejemplos [data-toast-root] apuntaban a un part INEXISTENTE — la deriva que el builder previene, demostrada en el propio doc). - MOR-1 escape isomorfo + validación de attr-names en semaSelector + 9 tests (selectors.test.ts, matches() real con comillas/corchetes) · MOR-2 partMarkerAttr = única fuente compilador↔builder + test de paridad · MOR-3 _resetCompileCache borrado (0 usos). - SOM-2 keydown continue en match sin handler + keyboardFixtureMorfo · SOM-1 no-await de handlers (censo async = 0; contrato V1 cumplido) + pin. - SEM-2 trigger pre-attacha catch con logger (void trigger sin unhandled rejection; throw intacto para awaiters) + pin · SEM-3 fallback muerto de applyDominance → skip defensivo + timer tope de awaitExpression cancelado · SEC-1 adjudicado YA implementado (assertCssVariableValue desde 2026-05-11) + pin del path de VALOR. - accordion → outline (§32; su outline:none dejaba CERO anillo en HCM) — verificado en vivo · THM-6 radius-full 9999px · EID-4 recuentos 33. F3 — censos con guard: - SOM-3 cerrado: announcer + image-provider migrados a scheduler-preferred (consumidores cableados: date/time-field vía soma.uix.timers; avatar/image vía eidos.timers — verificado en vivo); guard de timers ENSANCHADO de soma/components a TODO soma y pasado a EVIDENCIA (setTimeout exige .schedule( en el fichero — layers/ y datetime/ escapaban del ámbito viejo). - THM-5: R-4.7 nueva (válvula same-line /* important: <razón> */, escaneo comment-blanked) + las 15 declaraciones anotadas con su razón + canon recipe-contract §3/§4. - SOM-4 adjudicado: el censo/guard YA existían (49 pins); knob/mask-field/ timeline pinneados (overrides documentados en call-site); media-player Batch-4 (35 hits, cero renderProps) = único batch restante, registrado. - THM-4 doctrinado en eidos.md §unused (comportamiento/composición = legítimo; deuda = eje visual sin consumidor; hotspots por lotes). F4-C — corpus documental (decisiones de usuario aplicadas): - DOC-3: los 15 enlaces muertos resueltos (repoint a la edición FINAL trackeada / des-link históricos) · docs:check I6-links WARN→ERROR. - DOC-1: tabla «Build contract» MIGRADA a component-guide con estados modernizados (A3–A5 → LIVE + guards de hoy); banners reapuntados; citas de CANON/sema.md historificadas; lápida-redirect en el §13 del fósil. - DOC-4: hold chain → holds.ts · FAQ event:* SUPERSEDED por signatures · gradient añadido a los DOS capstones (sextet real) · nota de paleta de demo-authoring corregida (universalPaletteDecls + decisión THM-2 = mecanismo universal como sucesor del tracker borrado). - DOC-5/6: recuentos anti-frágiles datados · §4.11 dup → §4.12 · Known gaps historificado · N-6/N-7 recuperadas de git (d68d2c45^) y canonizadas en eidos.md §pickers · authoring E2 → canon/tsc.md · air-old des-linkado · EID-3 (placement) en la fila RTL · AUX-2 disabledDom documentado. SEM-4 sesión 1 — el close polimórfico de los pickers, VIVO (D.11): - Reconciliación: los morfos ya no declaran close (delegated al Popover, de-dialoged 06-27); el agujero real era el cierre programático bypaseando dismissWith → save/cancel/select eran perceptualmente SILENCIOSOS. - Fix: PickerShellHandle.setPopoverDismiss + closeWith(cause) en los 5 providers (14 sitios; select/commit → 'save' = commit.save+fulfill, cancel → 'cancel' = emerge; fallback raw para headless) + UN inyector en el eidos PickerShell root (norma N-8). Picker genérico fuera a propósito (ya suena commit-set/cancel por diseño S9). - Verificado en vivo (date-picker): Done → close·commit·fulfill·active · Cancel → close·emerge · cierre real. Gates: matriz 141/141 (los 6 morfos nuevos de la pista de texto paralela también PASS) · contracts 38/38 · eidos 314 · sema 178 · morfo 94 · docs:check 0/0 con I6 en error · baseline propio 57. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
3 months ago |