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 }
411 Commits (f4ce006423bc160a5283ad9666f22bff989f36aa)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
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 |
|
|
b92dc3bdb2 |
uix(card): el estado deshabilitado deja de pelearse con la animación por la opacidad
Salió midiendo el plan actual de pricing: la tarjeta no se atenuaba. La regla estaba escrita, el token resolvía a 0.4 sobre la propia tarjeta y el cursor de esa misma regla sí aplicaba — pero la opacidad computada seguía en uno. La culpable es su propia animación de entrada. Corre con relleno en los dos extremos y su último fotograma es opacidad uno; una animación gana a una declaración normal en la cascada, así que fijaba la propiedad para siempre. Aislado en el mismo nodo desactivando la entrada, atenúa correctamente. Llevaba ahí desde que el componente existe, invisible porque el cursor hacía parecer que el estado estaba cableado, y alcanzaba a toda Card deshabilitada del ecosistema. El arreglo es dejar de compartir la propiedad: el estado atenúa por filter, que la animación no posee. Mismo token, misma escala, ninguno nuevo, y el bloqueo del puntero en la tarjeta interactiva no se toca. El guard afirma la FORMA con su porqué escrito —que la regla usa filter y no opacity, más el bloqueo del puntero y la presencia de la animación que obliga a esa forma— y está probado por mutación: devolviendo la regla a opacity, falla; restaurada, verde. Verificado con la animación presente: la deshabilitada computa filter opacity a 0.4 y sus hermanas ninguno; en píxeles, su tinta más oscura sube a 164 frente a 139 de la normal sobre el mismo lienzo, que es justo lo que da un 0.4 sobre el fondo de página. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
d61515027a |
docs(theming): el changelog vuelve a su formato — mi entrada no traía 400 líneas
Al escribir la §49 pasé prettier por el fichero y reformateó los ejemplos de código de entradas ajenas: 237 inserciones y 185 borrados de los que apenas veinte eran míos. Es justo lo que el handoff del tier advierte de los ficheros que ya venían con otro formato — formatearlos produce un diff del documento entero y sepulta el cambio real. El fichero vuelve al formato que tenía y la entrada se re-aplica sin pasar prettier. Efecto neto contra el estado anterior al commit: 43 líneas, todas de la §49. No reescribo el commit porque la rama es compartida. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
d38c54fdf8 |
uix(card): el realce al puntero deja de ser rehén de `interactive`
El usuario preguntó por qué una tarjeta de plan de precios no responde al puntero. Le dije que Card no exponía estado de puntero y era falso: el realce existe desde siempre —los dos tokens están en la fundación, con reglas para ghost, outline y solid, composición con el anillo de selección para que no desaparezca al pasar el ratón, y anulación bajo reduced-motion—, pero todo colgaba de un solo attr. Y ese attr empaqueta cinco cosas: el elemento button, el evento commit-select, el cursor, la escala de pulsación, el anillo de foco y la elevación. Una tarjeta que ya contiene su propia llamada a la acción no puede tomar ese paquete: un button dentro de otro es marcado inválido y las dos activaciones se pelean por el mismo gesto. Así que la elevación era inalcanzable justo donde más se pide — planes de precio, teasers de artículo, tarjetas de testimonio. La elevación pasa a su propio eje. `interactive` lo implica, así que una tarjeta clicable no cambia en nada. Lo que el eje NO trae, a propósito: ni cursor de puntero —un puntero sobre algo que al pulsarlo no hace nada es una promesa falsa—, ni escala de pulsación, ni anillo de foco. Eso es lo que un CONTROL le debe al visitante. Sin tokens nuevos: los dos que hacían falta ya estaban. Y el attr es de envoltorio visual, no contrato — lo estampa el componente de eidos y lo guarda la mitad de presencia, que gana su primera fila de card; no el morfo, porque declarar ahí lo que sólo lee una capa es justo lo que la doctrina del 15 de agosto dejó dicho. El eidos-lint lo clasifica como eidos-only, 0 inválidos. Medido en navegador sobre estilos computados y posición real: apagado, el hover no mueve nada ni pinta sombra —cero regresión—; puesto, translateY de −2px y sombra de 18/48, con la tarjeta todavía div, cursor auto y su CTA intacto dentro, sin un solo botón anidado en botón; bajo reduced-motion el desplazamiento se anula y la sombra se queda, que es suprimir el movimiento sin perder la señal de profundidad; y una Card interactive de verdad —cinco en la demo del canon— sigue siendo button, con cursor de puntero y los mismos dos píxeles de elevación. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
2 months ago |
|
|
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 |
|
|
7ea913ab11 |
refactor(eidos): la rejilla de colocacion logica se canoniza — cinco declaraciones a mano pasan a una
`lib/types.ts` ya tenia `Position`, la rejilla 3x3 FISICA, con su idioma `Extract<Position, …>` y sus consumidores (Dialog, Drawer, Toast). Lo que no tenia nombre era la otra: la LOGICA, escrita a mano cinco veces — `AffixPlacement`, `FabPlacement`, `MenuDialPlacement`, `OnionPlacement` y las cuatro esquinas de `AvatarBadgePosition`—, coincidiendo por mantenimiento manual y no por contrato. Anadir una zona a una no llegaba a las otras cuatro. Ahora hay `LOGICAL_POSITIONS` / `LogicalPosition` junto a `POSITIONS` / `Position`, y las cinco estrechan desde ella. Ambas pasan a ser consts para que `docs:vocabularies` las genere: eran las unicas props visuales compartidas que faltaban en `docs/canon/vocabularies.md`, que ya listaba tamanos, variantes, paletas, arquetipos y el vocabulario sema entero. NO son dos grafias de lo mismo, y por eso ninguna sustituye a la otra: son dos COMPORTAMIENTOS. `top-left` es la izquierda de la pantalla y no espeja nunca; `top-start` sigue la direccion de lectura — medido hoy, de `left: 0` a `right: 0` en RTL. La regla de eleccion queda escrita en los dos typedoc y en el canon generado: ¿tiene que voltearse para un lector de derecha a izquierda? Una tira fijada en `bottom-end` va en el borde de salida en ambas direcciones; un panel que se abre a la derecha fisica porque ahi hay sitio, no. Con eso se cierra EID-3, que el audit de julio dejo asentado como excepcion pero marcado «placement fisico — pendiente de doctrina explicita». La doctrina ya no esta pendiente: son dos rejillas nombradas con la regla de cuando usar cada una. Sin cambio de comportamiento: las uniones resultantes tienen los mismos miembros (`MenuDialPlacement` = la rejilla + `static`; `FabPlacement` = las cuatro esquinas + `static`; `OnionPlacement` y `AffixPlacement` = la rejilla entera; `AvatarBadgePosition` = las cuatro esquinas). check 69 = base intacta · docs:check 0/627 · component:audit los cinco PASS · layer:check 0/3 · rtl 0/178 · smoke 311/311. 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 |
|
|
f1b672da79 |
docs: la doctrina alcanza a los cambios de la jornada — cinco backlogs y una llave de mas
Auditoria de deriva doc<->codigo sobre los 33 commits del 2026-08-13/14: cinco sitios seguian describiendo lo que el codigo dejo de hacer ayer. Se corrigen COMO BITACORA, al final de cada fichero (el cuerpo es el acta de lo que se firmo, no el estado del codigo; reescribirlo falsearia la firma). El formato es el que ya existia en `arts/adom` y `soma/textarea`: `## Backlog`. - `decisions/book-deviations.md` — dos entradas. D.7 nombraba `IntentExpectedFamily`, tipo que M6 ( |
2 months ago |
|
|
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 |