You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/AUDIT-blocks-ledger.md

1987 lines
291 KiB

fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
# AUDIT — tier `blocks`: registro de hallazgos (ledger)
> **Fuente única del estado de calidad del tier desde 2026-08-05.**
> Sustituye a la clasificación de
> [`AUDIT-blocks-2026-08-01.md`](./AUDIT-blocks-2026-08-01.md), que se conserva
> intacta: sus **reclamaciones, evidencias y sugerencias son válidas** y esta tabla
> las indexa. Lo que no sirve de aquel documento es el veredicto.
## Por qué existe este fichero
En el documento del 2026-08-01 los campos «Veredicto» están **desemparejados de su
hallazgo**: el de `cta`/`Container` lleva un veredicto sobre `Card`/`BoxProps`; el
del contraste de `hero`, uno sobre el drawer de `site-header`; el del import de
`$libs/forms`, uno sobre `--_link-palette-text`. La lista de refutados arrastra el
mismo desplazamiento: su primera justificación refuta el hallazgo de `feature-grid`
que la sección de confirmados declara CONFIRMADO.
La reclamación, la evidencia y la sugerencia sí casan entre sí dentro de cada
entrada. Lo que viajó movido es el veredicto — y con él la etiqueta
CONFIRMADO/REFUTADO. La causa más probable es un **join por posición** entre el
array de veredictos y el de hallazgos: un agente caído desplaza la lista entera. El
journal del workflow era de sesión y ya no existe, así que el emparejamiento
original no se puede recuperar: **hay que volver a medir**.
## Las reglas de este registro
1. **Toda escritura es por `id`.** Ningún proceso vuelve a unir dos listas por
posición. Un verificador que no devuelve su `id` no escribe fila.
2. **Las 90 filas parten de `PENDIENTE`**, incluidas las 29 «confirmadas» y las 11
«refutadas»: su etiqueta viajó con el veredicto movido y no es evidencia de nada.
3. **El `id` es re-derivable**: es el orden documental del fichero del 2026-08-01
(`A-01`…`A-29` confirmados · `A-30`…`A-79` sin verificar · `A-80`…`A-90`
refutados). Se extrae mecánicamente; no se renumera nunca.
4. **El veredicto se escribe con su mecanismo y su evidencia**, en la ficha de abajo.
Un `REFUTADO` sin motivo escrito es exactamente el fallo que estamos arreglando.
Estados: `PENDIENTE` · `CONFIRMADO` · `REFUTADO` · `DATO` (cierto y sin defecto) ·
`ARREGLADO` · `DIFERIDO` (confirmado, disposición firmada, no se toca ahora).
Columna **or.** = de qué sección del documento viejo salió la fila (`C` confirmado ·
`SV` sin verificar · `R` refutado). Es trazabilidad, **no** un veredicto.
## Índice de hallazgos
| id | block | dim | sev | or. | afirmación | ancla | estado |
| ---- | ---------------------------------------- | ---------- | ----- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------- |
| A-01 | `cta` | doctrina | ALTA | C | La disposición «Panel a sangre → app-land, la app pone `container="full"`» es falsa: `Container` siempre aplica padding inline y el block no ofrece… | `src/uix/blocks/cta/README.md` | ARREGLADO |
| A-02 | `hero` | percepcion | ALTA | C | En `layout="background"` la CTA secundaria mide 1.78:1 contra su fondo — muy por debajo de AA — y el token que la demo escribe a mano para arreglarlo… | `web/routes/blocks/hero/HeroSite.svelte` | ARREGLADO |
| A-03 | `feature-grid` | doctrina | ALTA | C | El README no declara landmark ni cómo ajustar la jerarquía de encabezados, que es obligación explícita de B-8 y de la plantilla; sus dos hermanos sí… | `src/uix/blocks/feature-grid/README.md` | ARREGLADO |
| A-04 | `contact` | doctrina | ALTA | C | `CONTACT_ACTION` («la acción nombra lo que está pasando», state.ts:86) solo se cumple a la vista: el nombre accesible del botón es siempre «Enviar»,… | `src/uix/blocks/contact/state.ts` | ARREGLADO |
| A-05 | `contact` | doctrina | ALTA | C | El block importa de `$libs/forms`, que no está en la lista de importaciones permitidas de la frontera dura 1 («may import `$uix`, `$adom` and the… | `src/uix/blocks/contact/contact.svelte` | ARREGLADO |
| A-06 | `site-footer` | percepcion | ALTA | C | El éxito del alta en la banda de sign-up del footer es PERCEPTIVAMENTE NULO: no suena, no estampa y no emite — el único momento que merecía… | `web/routes/blocks/site-footer/SiteFooterSite.svelte:105` | ARREGLADO |
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
| A-07 | `faq` | doctrina | MEDIA | C | La sección «Coordination» declara UNA sola costura (`value`) pero el block también expone `disabled`: una pregunta que no se puede abrir, sin nada… | `src/uix/blocks/faq/types.ts` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-08 | `cta` | doctrina | MEDIA | C | La medida de la descripción se construye a mano con un valor fuera de escala (`60ch`) en vez de usar el vocabulario de medida que el canon ya expone… | `src/uix/blocks/cta/cta.svelte` | ARREGLADO |
| A-09 | `stats-band` | doctrina | MEDIA | C | En modo `live` el anuncio a lectores de pantalla puede llevar la cifra CRUDA mientras en pantalla se ve la formateada por locale: el block pasa… | `src/uix/blocks/stats-band/stats-band-value.svelte` | CONFIRMADO |
| A-10 | `site-header` | doctrina | MEDIA | C | Si el cajón está abierto y el viewport cruza el `breakpoint`, el propio block esconde cajón + overlay + trigger con `display:none`, pero el Drawer… | `src/uix/blocks/site-header/site-header.svelte` | ARREGLADO |
| A-11 | `site-header` | doctrina | MEDIA | C | Las demos de site-header y hero redeclaran a mano uniones que ya existen en el canon, y ya han derivado: el `xxl` de `ContainerSize` es inalcanzable… | `web/routes/blocks/site-header/SiteHeaderSite.svelte` | CONFIRMADO |
| A-12 | `feature-grid` | doctrina | MEDIA | C | El tipo y la demo describen el chip del icono como `Surface` soft; el código lo pinta solid por defecto, y el README dice solid. Dos de las cuatro… | `src/uix/blocks/feature-grid/types.ts` | ARREGLADO |
| A-13 | `testimonials` | percepcion | MEDIA | C | Mismo defecto: `height="100%"` sobre `Card` es inerte, así que la tarjeta no rellena la altura de fila y las caras NO se alinean cuando las citas… | `src/uix/blocks/testimonials/testimonials-item.svelte` | ARREGLADO |
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
| A-14 | `feature-split · pricing · testimonials` | doctrina | MEDIA | C | Ninguno de los tres README declara el landmark ni la jerarquía de encabezados, que es la segunda mitad literal de B-8 y el único apartado a11y propio… | `src/uix/blocks/pricing/README.md` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-15 | `contact` | doctrina | MEDIA | C | La frase del estado no llega a la tecnología de apoyo: el submit va `disabled` nativo (no es alcanzable con teclado, así que un lector de pantalla… | `src/uix/blocks/contact/contact-reason.svelte` | ARREGLADO |
| A-16 | `contact` | doctrina | MEDIA | C | Deriva README↔código: el README afirma en negrita que el block «no valida y no pinta los errores de los campos», y `Contact.Fields` hace exactamente… | `src/uix/blocks/contact/README.md` | ARREGLADO |
| A-17 | `content-section` | doctrina | MEDIA | C | La pestaña API de la demo (superficie obligatoria por B-9) publica un default falso para `ContentSection.Media`: dice `width` «def. wide» cuando el… | `web/routes/blocks/content-section/+page.svelte` | ARREGLADO |
| A-18 | `team` | doctrina | MEDIA | C | Comentario nuevo en castellano dentro de la fuente de un block, contra la regla del proyecto (comentarios en inglés; los castellanos que quedan son… | `src/uix/blocks/team/team-member-links.svelte` | ARREGLADO |
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
| A-19 | `faq` | percepcion | BAJA | C | El typedoc de `Faq.Header` promete un Box «centered» que el propio root desmiente: la raíz apila con `align="stretch"`, así que la cabecera queda… | `src/uix/blocks/faq/types.ts` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-20 | `faq` | doctrina | BAJA | C | B-8 incumplido: el block emite un encabezado por pregunta con `aria-level` FIJO en 3, no ofrece forma de ajustarlo y el README no documenta ni el… | `src/uix/blocks/faq/README.md` | ARREGLADO |
| A-21 | `stats-band` | doctrina | BAJA | C | El typedoc de `countOptions` anuncia una opción `style` que `CountUp` NO acepta — pasarla es error de tipos. | `src/uix/blocks/stats-band/types.ts` | ARREGLADO |
| A-22 | `stats-band` | doctrina | BAJA | C | El README no documenta landmark ni jerarquía de encabezados (B-8); el dato solo vive en la pestaña A11y de la demo. | `src/uix/blocks/stats-band/README.md` | ARREGLADO |
| A-23 | `pricing` | doctrina | BAJA | C | Pulsar el segmento de periodo YA ACTIVO devuelve la sección entera al periodo contrario: el block posee el estado y su propio control lo mueve a un… | `src/uix/blocks/pricing/pricing-switch.svelte` | ARREGLADO |
| A-24 | `pricing` | percepcion | BAJA | C | Las tarjetas de plan NO son de igual alto y los CTA NO se alinean: `height="100%"` sobre `Card` es un atributo HTML inerte, no una prop. El README y… | `src/uix/blocks/pricing/pricing-plan.svelte` | ARREGLADO |
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
| A-25 | `pricing` | doctrina | BAJA | C | La demo documenta una elevación que el código se niega explícitamente a hacer y que el propio README lista como brecha abierta del canon. | `web/routes/blocks/pricing/+page.svelte` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-26 | `pricing` | doctrina | BAJA | C | El block exige a la app tres cadenas obligatorias para nombrar el ÚNICO estado que coordina, y justifica esa exigencia citando una lectura de B-7 que… | `src/uix/blocks/pricing/pricing-switch.svelte` | REFUTADO |
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
| A-27 | `testimonials` | doctrina | BAJA | C | El tipo público documenta un default de `variant` que el componente no usa. | `src/uix/blocks/testimonials/types.ts` | ARREGLADO |
| A-28 | `testimonials` | doctrina | BAJA | C | `.Items` tipa toda la superficie de `AutoGrid` pero descarta en silencio parte de ella: el spread del consumidor va ANTES de props fijadas a mano. | `src/uix/blocks/testimonials/testimonials-items.svelte` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-29 | `pricing · testimonials` | percepcion | BAJA | C | Las vistas de 375/768 de estas dos demos no heredan NINGUNO de los ejes de la sección: siempre salen en claro, español y ltr — justo la rama estrecha… | `web/routes/blocks/pricing/+page.svelte` | CONFIRMADO |
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
| A-30 | `newsletter` | doctrina | ALTA | SV | UNA sola pulsación de Enter dentro del campo de correo emite DOS `commit-submit` con intents contradictorios (neutral y fulfill) y suenan DOS earcons… | `src/uix/blocks/newsletter/newsletter.svelte:105` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-31 | `newsletter` | doctrina | ALTA | SV | `signal-warn-invalid` está declarado `persistence:'untilFix'` pero NUNCA se limpia al arreglar: corregido el correo, el form sigue estampado en… | `src/uix/soma/components/form/form-provider.svelte.ts:129` | REFUTADO |
| A-32 | `site-header` | percepcion | ALTA | SV | El pack de sema de NavigationMenu NUNCA casa: su regla de cascada apunta a la parte `item` y el estampado cae siempre en el `trigger`, asi que el nav… | `src/uix/sema/components/navigation-menu.ts` | ARREGLADO |
| A-33 | `site-header` | percepcion | ALTA | SV | El nav del header emite `commit-select` + `affirm` — con earcon audible — por el simple HOVER del puntero, sin un solo clic: un barrido del raton… | `src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.ts` | ARREGLADO |
| A-34 | `site-header` | percepcion | ALTA | SV | Inversion de dominancia atencional: pasar el raton por encima del nav suena mas largo y mas fuerte que pulsar el CTA principal del block, y con… | `web/routes/blocks/site-header/SiteHeaderSite.svelte` | ARREGLADO |
| A-35 | `site-header` | percepcion | ALTA | SV | MUDOS: 4 de los 7 elementos interactivos visibles del header no emiten NADA, y son precisamente los que navegan — el brand «Acme», los… | `web/routes/blocks/site-header/SiteHeaderSite.svelte` | ARREGLADO |
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
| A-36 | `site-header` | percepcion | ALTA | SV | Carrera de estampado en el trigger del drawer: `contact-activate` vive 3 ms antes de que `emerge.open` lo pise en el MISMO nodo, asi que el feedback… | `src/uix/blocks/site-header/site-header.svelte` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-37 | `banner` | doctrina | ALTA | SV | El block cablea el descarte con `variant="ghost"` y sin tinta, así que la ✕ se pinta SIEMPRE con el ink `primary` del Button sobre el lienzo que el… | `src/uix/blocks/banner/banner.svelte` | ARREGLADO |
| A-38 | `banner` | percepcion | ALTA | SV | La pestaña «Notas» afirma que la acción en línea HEREDA la tinta de contraste de la tira «en todos los roles y en los dos modos». La medición lo… | `web/routes/blocks/banner/+page.svelte` | ARREGLADO |
| A-39 | `newsletter` | doctrina | ALTA | SV | El ejemplo de uso de la puerta de entrada del block anuncia `onValidSubmit` — exactamente el prop que el block decidió NO exponer porque es un no-op… | `src/uix/blocks/newsletter/index.ts` | ARREGLADO |
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
| A-40 | `contact` | percepcion | ALTA | SV | El clic sobre «Enviar mensaje» —la accion principal de la seccion— no estampa NINGUN evento sema en el momento del gesto; la unica emision llega 915… | `src/uix/blocks/contact/contact-submit.svelte` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-41 | `contact` | percepcion | ALTA | SV | Un solo Enter dentro de un campo estampa DOS commits con intents distintos; y cuando el envio esta bloqueado estampa un commit que no commitea nada. | `src/uix/morfo/components/field.ts` | ARREGLADO |
| A-42 | `contact` | percepcion | ALTA | SV | El nombre accesible del boton de envio queda congelado en «Enviar» en los cinco estados: las palabras de estado que el block posee por B-7 nunca… | `src/uix/blocks/contact/contact-submit.svelte` | ARREGLADO |
| A-43 | `contact` | percepcion | ALTA | SV | `Contact.Reason` —la frase que el block existe para que ningun estado bloqueado quede mudo— no es region viva ni esta enlazada al control: cambia sin… | `src/uix/blocks/contact/contact-reason.svelte` | ARREGLADO |
| A-44 | `contact` | doctrina | ALTA | SV | Con los tres campos rellenos y el correo mal formado, la seccion afirma «Completa todos los campos para enviar.» y no marca el campo: el camino de… | `src/uix/blocks/contact/contact-fields.svelte` | ARREGLADO |
| A-45 | `pricing` | percepcion | ALTA | SV | Un solo clic sobre el periodo YA activo VACIA el segmented control: la seccion queda sin ningun periodo marcado mientras los precios siguen siendo… | `src/uix/blocks/pricing/pricing-switch.svelte` | ARREGLADO |
| A-46 | `cta` | percepcion | ALTA | SV | La accion secundaria del cluster («Hablar con ventas») es MUDA: no emite ningun evento sema, y en reposo es tipograficamente identica al parrafo de… | `web/routes/blocks/cta/CtaSite.svelte:72` | REFUTADO |
| A-47 | `cta` | percepcion | ALTA | SV | El anillo de foco es practicamente invisible sobre el panel solido: los DOS unicos elementos interactivos del block quedan sin indicador de foco por… | `src/uix/blocks/cta/cta.svelte:111` | CONFIRMADO |
| A-48 | `banner` | percepcion | MEDIA | SV | El descarte —el ÚNICO gesto que el block cablea— sólo expresa `contact-activate` (una pulsación); la desaparición de la tira no la expresa nadie, y… | `src/uix/blocks/banner/banner.svelte:59` | CONFIRMADO |
| A-49 | `banner` | percepcion | MEDIA | SV | La única llamada a la acción del banner («Ver qué cambia →») es completamente muda: ni estampa ni suena, en clic, en Enter ni en hover. | `web/routes/blocks/banner/BannerSite.svelte:56` | REFUTADO |
| A-50 | `site-footer` | percepcion | MEDIA | SV | 18 de los 24 elementos interactivos del footer son `Link` y TODOS son mudos: el block cuya función entera es navegar no tiene canal perceptivo para… | `src/uix/blocks/site-footer/site-footer.svelte:77` | REFUTADO |
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
| A-51 | `newsletter` | doctrina | MEDIA | SV | La acción principal del block no tiene `contact` propio: pulsar «Suscribirme» no estampa NADA sobre el control — el único estampado es el `commit`… | `src/uix/morfo/components/form.ts:90` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-52 | `site-header` | percepcion | MEDIA | SV | La costura `mobileOpen` cierra el drawer en SILENCIO: el unico camino de cierre que el block documenta como suyo es el unico que no se anuncia,… | `src/uix/blocks/site-header/site-header.svelte` | REFUTADO |
| A-53 | `hero` | percepcion | MEDIA | SV | Las dos acciones del hero viven en el mismo `Group`, con la misma jerarquia de par primario/secundario, y responden de forma opuesta: el CTA emite… | `web/routes/blocks/hero/HeroSite.svelte` | CONFIRMADO |
| A-54 | `site-header` | doctrina | MEDIA | SV | El morfo de `NavigationMenu` carga el intent evaluable en el MARCO: declara un unico evento, `commit-select` + `affirm`, para lo que solo despliega… | `src/uix/morfo/components/navigation-menu.ts` | ARREGLADO |
| A-55 | `banner` | doctrina | MEDIA | SV | «full-bleed por fuera, en columna por dentro» deja de ser cierto en cuanto se pasa `onDismiss`: el `margin-inline-start:auto` del Close es un TERCER… | `src/uix/blocks/banner/banner.svelte` | CONFIRMADO |
| A-56 | `banner` | doctrina | MEDIA | SV | B-9 exige el block A SANGRE en la página de demo, «nunca dentro de un marco con padding». En la galería el block se pinta dentro del raíl del… | `web/routes/blocks/_lib/BlockDemo.svelte` | REFUTADO |
| A-57 | `newsletter` | percepcion | MEDIA | SV | Con `panel={false}` la nota de privacidad se pinta con MÁS fuerza que la línea de apoyo: la descripción cae a `muted` y la nota se queda con la tinta… | `src/uix/blocks/newsletter/newsletter.svelte` | ARREGLADO |
| A-58 | `site-footer` | doctrina | MEDIA | SV | La demo construye tintas a mano en app-land (`color="var(--color-content-*)"`) y son INERTES: con `variant="subtle"` el recipe del Link fuerza… | `web/routes/blocks/site-footer/SiteFooterSite.svelte` | ARREGLADO |
| A-59 | `site-footer` | percepcion | MEDIA | SV | El título del grupo se pinta MÁS PEQUEÑO que los enlaces que nombra y con la misma tinta: la única señal de jerarquía que queda es el peso. | `src/uix/blocks/site-footer/site-footer-column-title.svelte` | ARREGLADO |
| A-60 | `site-footer` | percepcion | MEDIA | SV | «Tantas columnas como caben, el mismo código para 2 o para 5 grupos» describe el código, no el resultado: `AutoGrid` usa `auto-fill`, que crea las… | `src/uix/blocks/site-footer/site-footer.svelte` | CONFIRMADO |
| A-61 | `contact` | percepcion | MEDIA | SV | El submit bloqueado sale del orden de tabulacion, asi que quien navega con teclado nunca llega al control ni se topa con la frase que lo explica. | `src/uix/blocks/contact/contact-submit.svelte` | CONFIRMADO |
| A-62 | `team` | percepcion | MEDIA | SV | Los 12 IconButton que el block pone en escena estampan contact-activate y no hacen absolutamente nada: el contacto inicia y nada resuelve, doce veces. | `web/routes/blocks/team/TeamSite.svelte` | CONFIRMADO |
| A-63 | `contact` | percepcion | MEDIA | SV | El `Link` «hola@vicen.dev» dentro de `Contact.Detail` es mudo: es el unico elemento interactivo de la columna de datos y no emite nada. | `web/routes/blocks/contact/ContactSite.svelte` | REFUTADO |
| A-64 | `pricing` | percepcion | MEDIA | SV | Ese mismo clic muerto emite `commit-toggle` + intent `neutral`: el sistema anuncia que algo quedo fijado cuando la seccion no cambio de estado (los… | `src/uix/blocks/pricing/pricing-switch.svelte` | ARREGLADO |
| A-65 | `pricing` | percepcion | MEDIA | SV | Un solo gesto sobre el CTA de un plan produce DOS eventos sema sobre el MISMO elemento a 1 ms: el `commit-select` de la app pisa el… | `web/routes/blocks/pricing/PricingSite.svelte` | CONFIRMADO |
| A-66 | `pricing` | percepcion | MEDIA | SV | El plan destacado se grada `fulfill` en lo visual pero su consecuencia suena `affirm`, exactamente igual que los otros dos planes: la lectura… | `web/routes/blocks/pricing/PricingSite.svelte` | REFUTADO |
| A-67 | `faq` | percepcion | MEDIA | SV | Un solo clic que cambia de pregunta dispara DOS earcons emerge simultaneos y sin atenuar (collapse descendente + expand ascendente): un gesto, dos… | `src/uix/blocks/faq/faq-list.svelte:14` | CONFIRMADO |
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
| A-68 | `stats-band` | percepcion | MEDIA | SV | Los contadores corren detras de `opacity: 0` — dos observadores distintos (CountUp threshold 0 · Motion 0.1/-10%) y un IO no mira la opacidad… | `src/uix/blocks/stats-band/stats-band-value.svelte:20` | ARREGLADO |
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
| A-69 | `stats-band` | percepcion | MEDIA | SV | Las cuatro cifras no aterrizan nunca juntas y `duration` no significa segundos: con el valor por defecto (2), la cifra grande tarda 7,9 s en… | `src/uix/eidos/components/count-up/count-up.svelte:169` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-70 | `feature-grid` | percepcion | BAJA | SV | DATO, NO DEFECTO: `feature-grid` no tiene ningun elemento interactivo — nada que emitir y nada que auditar perceptivamente en el. | `src/uix/blocks/feature-grid/feature-grid.svelte` | REFUTADO |
| A-71 | `site-footer` | doctrina | BAJA | SV | La página de demo pinta el block ANTES de su propio `h1`, así que el documento arranca en h2/h3 y el h1 llega el último — jerarquía de encabezados… | `web/routes/blocks/_lib/BlockDemo.svelte` | CONFIRMADO |
| A-72 | `contact` | percepcion | BAJA | SV | La ventana de vuelo (900 ms) no tiene canal perceptivo propio: ningun `sustain.*`, ningun spinner — solo el cambio de etiqueta visible. | `src/uix/blocks/contact/contact-submit.svelte` | REFUTADO |
| A-73 | `content-section` | percepcion | BAJA | SV | DATO, no defecto: el block no pone NINGUN elemento interactivo — cero en toda su region — y nada en el estampa sema. | `web/routes/blocks/content-section/ContentSectionSite.svelte` | DATO |
| A-74 | `feature-split` | percepcion | BAJA | SV | Los 3 unicos elementos interactivos del block emiten su contact correctamente, pero no llevan a ningun sitio: los href apuntan a fragmentos que no… | `web/routes/blocks/feature-split/FeatureSplitSite.svelte` | CONFIRMADO |
| A-75 | `pricing` | percepcion | BAJA | SV | Volver a pulsar el CTA de un plan YA elegido re-emite un `commit-select`+`affirm` identico: no hay diferencia perceptiva entre «acabas de elegirlo» y… | `web/routes/blocks/pricing/PricingSite.svelte` | CONFIRMADO |
| A-76 | `testimonials` | percepcion | BAJA | SV | DATO, no defecto: el block no pone NINGUN elemento interactivo — no hay nada que pulsar y, por tanto, nada mudo; ademas sus Card no fingen afordancia. | `src/uix/blocks/testimonials/testimonials.svelte` | DATO |
| A-77 | `stats-band` | percepcion | BAJA | SV | DATO, no defecto: el block no pone NINGUN elemento interactivo — no hay nada que pulsar y por tanto nada que estampar. Toda su superficie perceptiva… | `src/uix/blocks/stats-band/stats-band.svelte` | DATO |
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
| A-78 | `stats-band` | doctrina | BAJA | SV | El <section> del block sale sin nombre accesible Y sin ningun encabezado dentro, asi que no es una region navegable ni por landmarks ni por… | `src/uix/blocks/stats-band/stats-band.svelte:26` | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-79 | `site-footer` | percepcion | SOSP | SV | Cambiar de idioma en el selector del footer cambia el régimen de toda la página (langs, formatos, dirección) pero suena y se estampa exactamente… | `web/routes/blocks/site-footer/SiteFooterSite.svelte:164` | CONFIRMADO |
| A-80 | `faq` | — | — | R | El block reimplementa la autogeneración del `value` que el canon ya hace, y README+index la venden como «la única conveniencia» de `.Item`. | — | ARREGLADO |
| A-81 | `cta` | — | — | R | El ejemplo del `index.ts` anuncia `variant="soft"`, una prop que se CORTÓ a propósito: copiarlo es un error de tipos. | — | ARREGLADO |
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
| A-82 | `cta` | — | — | R | La demo redeclara en app-land la unión que el block EXPORTA (`CtaLayout`), duplicando vocabulario. | — | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-83 | `hero` | — | — | R | El block deriva el estado «la copia va sobre sólido» y lo aplica solo a dos de sus piezas, sin publicarlo: cada consumidor lo re-deriva en el punto… | — | ARREGLADO |
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
| A-84 | `feature-grid` | — | — | R | `align` está aplicado a medias: el block declara la prop y solo mueve la caja de la cabecera; el propio tipo instruye al app a repetir la alineación… | — | ARREGLADO |
| A-85 | `site-header` | — | — | R | El `aria-label` que la demo escribe para el trigger del cajón nunca llega al DOM: el lector de pantalla anuncia la etiqueta genérica del canon. | — | ARREGLADO |
| A-86 | `pricing` | — | — | R | El ejemplo de uso de la superficie pública del block no compila: omite dos props obligatorias. | — | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-87 | `contact` | — | — | R | El propio gating del block deja INALCANZABLE el error por campo: con `progressive` los errores solo se escriben tras un envío inválido, y el envío… | — | ARREGLADO |
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
| A-88 | `team` | — | — | R | `marginTop: 'auto'` no ancla nada: el ítem de la retícula es el `<div>` del `Motion` (block, estirado), y el `Stack` de dentro mide su contenido, así… | — | ARREGLADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-89 | `contact` | — | — | R | `sent` es un estado TERMINAL del que el block no sabe salir, y lo entra él solo: tras un envío correcto el formulario sigue editable pero la sección… | — | REFUTADO |
| A-90 | `contact` | — | — | R | Vocabulario paralelo: `ContactVerification` re-declara palabra por palabra el `ProofOfHumanStatus` del canon, y lo hace señalando a ese mismo… | — | ARREGLADO |
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
| A-91 | `banner` | doctrina | BAJA | F7 | El block re-expone el eje `intent` de `Banner` —que el canon declara fuera del sistema de color abierto, en su propio typedoc— sin registrarlo en sus Gaps… | `src/uix/blocks/banner/README.md` | ARREGLADO |
| A-92 | `pricing` | doctrina | BAJA | F7 | Una restricción estructural del modelo de cascada (sólo los presets CSS participan del stagger) vive en un comentario del block y falta en §D.13 de la doctrina… | `docs/theming/motion.md` | CONFIRMADO |
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
| A-93 | `stats-band` | doctrina | MEDIA | F7 | `Motion` no expone su momento «visto» (privado + `data-animation-pending`), así que un block no puede sincronizarse con el revelado sin violar B-6… | `src/uix/eidos/components/motion/motion.svelte` | ARREGLADO |
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
| A-94 | `feature-grid · team · testimonials` | doctrina | MEDIA | F7 | Los tres declaran `= AutoGridProps` y ponen `{...rest}` antes de props fijadas: el consumidor tipa `align`/`width` y se descartan en silencio (A-28 ×3)… | `src/uix/blocks/testimonials/types.ts` | ARREGLADO |
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
| A-95 | `banner` | doctrina | MEDIA | F7 | Con `affix="top"` la tira fijada tapa la cabecera pegada y la navegación queda inalcanzable; la pieza que falta es la que posee las alturas de página (`app-shell`)… | `web/routes/blocks/banner/BannerSite.svelte` | CONFIRMADO |
| A-96 | `banner` | doctrina | BAJA | F7 | `affixOffset` es prop público sin control vivo en la demo, contra la regla «cada prop público, un control»… | `web/routes/blocks/banner/+page.svelte` | CONFIRMADO |
| A-97 | `feature-grid + 6 blocks` | percepcion | MEDIA | F7 | El texto `color="muted"` mide 3,70:1 en claro — bajo AA de cuerpo — en 8 sitios de 7 blocks; el hallazgo estaba enterrado en la ficha de A-70, que es REFUTADO… | `src/uix/blocks/feature-grid/feature-grid-item-text.svelte` | REFUTADO |
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
| A-98 | `hero` | percepcion | MEDIA | F7 | Las acciones no apilan en móvil: `Group` es row/nowrap y a 375px los dos botones ocupan 315 de 327px. Síntoma de [A-99]; cae sin tocar el block… | `src/uix/blocks/hero/hero.svelte` | ARREGLADO |
| A-99 | `Group` (canon) | doctrina | MEDIA | F7 | `Group` no aplica NINGUNO de los dos defaults de action row que su README promete (`wrap: wrap` y `align: center`): nunca se los pasa a `Flex`, y el recipe no puede suplirlos… | `src/uix/eidos/components/group/group.svelte` | ARREGLADO |
blocks(background): seis fondos, y el que parecía mejor era el que no se podía leer F5 de PLAN-background.md: los seis blocks de D-BG.10 (2) reciben `Background.*`. El hero cayó en F2 y la página compuesta ya existía — no había que construirla, había que verificarla con la decoración puesta. La forma es uniforme y tiene un solo desvío: cada block gana un prop `decor` que monta `<Background.Pattern>` como HIJA de su `Section`, la misma composición que el hero ya probó. El desvío es `cta`, donde la decoración va dentro del PANEL, que es donde está el ojo. **Dos hallazgos que sólo la medición podía dar.** Dentro del panel sólido del `cta`, las dos tramas TINTADAS hunden el titular bajo AA: `glow` lleva la copia blanca de 5,18:1 a **3,06:1** y `mesh` a **2,27:1**. Pintan un lavado claro anclado en `50% 0%` — exactamente donde se asienta el titular. Las de línea toman la tinta de regla, oscurecen, y la copia sube a **14,35:1** mientras la textura sigue leyéndose a 3,19:1 contra el panel. Por eso el default es `rings`, que además irradia del mismo anclaje superior que usaba el glow. Las tintadas se ofrecen igual: el app decide, pero ya con el número delante, en el tipo y en el README. Y una trama tintada dentro del panel era invisible por construcción: `--_background-tint` cae en `--color-primary-solid`, que es EL MISMO valor que pinta el panel — un halo del color del panel sobre el color del panel. El block pasa ahora `color="var(--color-content-on-solid)"`, el token que ya usa para su propia copia; medido después, el tinte resuelve a `#ffffff`. **Los defaults, y por qué dos están apagados.** `cta` `rings` · `stats-band` `grid`, porque una banda de cifras se lee como medida y la rejilla es la trama que lo dice sin decorar por decorar · `testimonials` y `feature-split` `glow`. `site-footer` y `banner` reciben la capacidad APAGADA: la columna B del plan de calidad no pide acabado en ninguno de los dos, y encenderlo por nuestra cuenta sería inventar un aspecto que nadie pidió. **Un desvío de alcance, declarado.** La columna B pedía «`Backdrop` alterno» POR FILA en `feature-split`; se resuelve en la SECCIÓN. Una banda por fila obligaría a cada `Row` a poseer el estado de alternancia, y eso es coordinación — un block que coordina deja de ser un block que no posee nada. Medido en Chrome sobre la página compuesta real: cinco pilas, los cinco anfitriones adoptados (`position: relative` + `isolation: isolate`) sin que ningún block los posicione; la copia mide 15,88:1 sobre las secciones decoradas y 5,18:1 sobre el panel del cta — AA en todas. Sobre lienzo oscuro, medido aparte: 17,06 → 13,23:1 con `glow` y 12,37:1 con `grid`/`dots`. Ledger: seis filas nuevas, A-100…A-105, cada una con su mecanismo y su evidencia. Gates: `blocks:check` 0/18 · `rtl:check` 0/180 · `docs:check` 0/0 en 634 docs · 434/435 (el fallo es el `skin-media-player` de siempre) · `check` 0 errores propios · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-100 | `cta` | percepcion | ALTA | F5-bg | Dentro del panel sólido, las dos tramas TINTADAS hunden el titular blanco bajo AA: `glow` 5,18 → 3,06:1 y `mesh` → 2,27:1. Las de línea oscurecen y suben la copia a 14,35:1 | `src/uix/blocks/cta/cta.svelte` | ARREGLADO |
| A-101 | `cta` | doctrina | MEDIA | F5-bg | Un `glow` tintado con el acento del propio panel resuelve EXACTAMENTE al fondo del panel: el halo es invisible por construcción. El acento debe viajar como tinta on-solid | `src/uix/blocks/cta/cta.svelte` | ARREGLADO |
| A-102 | `stats-band` | percepcion | BAJA | F5-bg | Sin decoración de sección (columna B: «reveal + Backdrop»); el reveal ya estaba, el fondo no | `src/uix/blocks/stats-band/stats-band.svelte` | ARREGLADO |
| A-103 | `testimonials` | percepcion | BAJA | F5-bg | Sin decoración de sección; el halo detrás de las citas es el acabado que las referencias pintan a mano | `src/uix/blocks/testimonials/testimonials.svelte` | ARREGLADO |
| A-104 | `feature-split` | percepcion | BAJA | F5-bg | Columna B pedía «`Backdrop` alterno» por fila; se resuelve en la SECCIÓN — una banda por fila obligaría a cada `Row` a poseer estado de alternancia | `src/uix/blocks/feature-split/feature-split.svelte` | ARREGLADO |
| A-105 | `site-footer + banner` | doctrina | BAJA | F5-bg | Ambos ganan la capacidad de decoración APAGADA por defecto: la columna B no pide acabado en ninguno, y encenderlo sería inventar un aspecto que nadie pidió | `src/uix/blocks/site-footer/site-footer.svelte` | ARREGLADO |
| A-106 | `Button` (canon) | doctrina | MEDIA | F2b-D | `Button` acepta `ref` en su tipo y NUNCA lo reenvía: `bind:ref` tipa bien y no ata nada. Forma exacta de [A-94] — un tipo que promete una superficie que descarta | `src/uix/eidos/components/button/button.svelte` | CONFIRMADO |
| A-107 | `Switch` (canon) | doctrina | BAJA | F2b-D | El morfo declara dos partes (`provider` + `thumb`) y ninguna es etiqueta: la etiqueta visible es hermana, no es diana de clic y sólo se ata por `aria-labelledby` | `src/uix/morfo/components/switch.ts` | CONFIRMADO |
| A-108 | `Dialog` (canon) | doctrina | MEDIA | F2b-D | Al cerrar devuelve el foco al nodo que lo tenía al abrir; si ese disparador se desmontó, acaba en el `body` — y la trampa sigue armada un frame más | `src/uix/soma/components/dialog/dialog-provider.svelte.ts` | CONFIRMADO |
| A-109 | `Banner` (canon) + `banner` | doctrina | MEDIA | F2b-D | DOS landmarks `banner` en toda página con cabecera: el canon estampa `role="banner"` DESPUÉS de los rest props, así que nombrar ambos es lo único que queda | `src/uix/eidos/components/banner/banner.svelte` | CONFIRMADO |
| A-110 | `feature-split` | doctrina | MEDIA | F2b-D | Único block de sección sin parte `.Header`: sus filas emiten `h2`, así que en una página compuesta la sección no tiene nombre y aporta tres `h2` hermanos | `src/uix/blocks/feature-split/types.ts` | CONFIRMADO |
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## Reparto
| Dimensión | Filas | Cómo se verifica |
| -------------------------------- | ----- | ------------------------------------------------------------------------------------------- |
| doctrina | 35 | lectura dirigida de la fuente que ancla la fila, contra `architecture/blocks.md` |
| percepcion | 44 | navegador con sondas (audio + MutationObserver al nodo), tras la puerta de hidratación real |
| sin dimensión (los 11 refutados) | 11 | se clasifica al verificar |
Severidad ALTA declarada en origen: **24** filas.
## Fase 1 — doctrina re-verificada por lectura (2026-08-05)
43 filas cerradas leyendo la fuente que cada una ancla: **39 CONFIRMADO · 4 REFUTADO**. Lo que la
re-verificación cambió respecto de lo que decía el documento viejo:
- **A-04 (`contact`, ALTA) — el hecho se sostiene, la CAUSA no.** El canon SÍ tiene escotilla: soma
`form-submit` acepta `aria-label` y `form-provider.svelte.ts:187-189` hace ganar al valor explícito
del consumidor. El nombre accesible está congelado porque `contact-submit.svelte:21` **no lo pasa**,
no porque el canon lo imponga. La sugerencia original mandaba a tocar soma sin necesidad.
- **Cuatro refutados con motivo escrito**: A-26 (la doctrina acota la propiedad de las palabras a las
secciones que pueden bloquear; `pricing` no bloquea nada), A-31 (`form-provider.svelte.ts:129` SÍ
limpia el `signal-warn-invalid`, en la línea que la propia reclamación anclaba), A-56 (B-9 prohíbe
marco con padding o caja con scroll, y la región de contenido no aporta ninguno de los dos),
A-89 (`sent` es un prop `$bindable`: la vuelta existe y es del app).
- **A-87 es duplicado de A-44** — la MISMA reclamación aparecía como «sin verificar» y como
«refutada» en el documento viejo. Por sí solo prueba que la clasificación estaba movida.
- **A-83 no era doctrinal, era la causa de A-02**: `hero` deriva `onDark` y solo lo aplica a dos
piezas, así que la tinta on-solid no llega a lo que el app pone en `actions` — de ahí el 1.78:1.
- **B-8 es más grave de lo que decía el handoff**: 8 de 15 READMEs sin declaración de landmark, no 6.
- **Tres puertas de entrada del tier tienen ejemplos que no compilan**: A-39 (`newsletter`),
A-81 (`cta`), A-86 (`pricing`).
## Fase 2 — percepción re-verificada en navegador (2026-08-05)
47 filas (44 de percepción + 3 que exigían medir) por workflow adversarial, en cuatro tandas,
**un agente por `id`**. Resultado del registro completo: **75 CONFIRMADO · 12 REFUTADO · 3 DATO**,
y 30 de las confirmadas ya ARREGLADAS.
- **La causa raíz del desastre anterior, reproducida y neutralizada**: el journal del workflow
devuelve los resultados en un orden distinto al de entrada (en la primera tanda salió `A-42`
primero y `A-02` penúltimo). Unir por posición habría vuelto a cruzar cada veredicto con la
reclamación equivocada. Aquí la unión es por `id` y el desorden es irrelevante.
- **La tasa de refutación real es del 13%** (12 de 90), no el 28% que declaraba el documento
viejo — y las refutaciones se concentraron en las tandas 2–4, así que no es complacencia:
`A-46`, `A-49`, `A-50`, `A-52`, `A-70`, `A-72` cayeron con motivo escrito.
- **Los verificadores corrigieron reclamaciones además de juzgarlas**: `A-02` mide 1.61:1, no
1.78:1; `A-42` afecta a los SIETE estados, no a cinco; `A-36` no trunca el feedback, NUNCA lo
pinta; `A-60` mide el 62% de región muerta con 2 grupos.
- **El retrato que sale del conjunto**: de las 12 ALTA de percepción, **8 tienen la causa raíz en
el CANON** (`NavigationMenu`, `Form.Submit`, morfo de `Field`, `Link`), no en los blocks. Los
blocks componen bien; lo que está roto es el arco perceptivo del canon.
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
## Fase 6 — re-medición de los confirmados (2026-08-10)
Las 33 filas que seguían CONFIRMADAS, medidas otra vez ANTES de tocar código. El
motivo: de las 12 ALTA de percepción, 8 tenían la causa raíz en el CANON, y el
canon se arregló el 2026-08-06 — cada fila de percepción viva era sospechosa de
estar ya cerrada. **Seis lo estaban** (A-07 · A-30 · A-40 · A-51 · A-78 · A-82),
y sus fichas llevan la evidencia. Quedan **27**.
Método: lectura dirigida para doctrina; para percepción, Playwright headless
contra un dev server propio (`:5201`), con la puerta de hidratación honesta
(`#uix-blocks-display` + ida y vuelta reactiva) y dos sondas — contador de nodos
WebAudio parcheado antes de cargar y `MutationObserver` de `data-event*` sobre
todo el documento.
**Lo que la re-medición corrige del método anterior**, y que vale para la
siguiente: el cero de la escala tiene que ser el GESTO. Anclado a la carga, un
`contact-activate` a +270 ms parece llegar tarde; con un CONTROL en el mismo
entorno (el `<Button>` canónico del `cta`, +299 ms) se ve que esos 270 ms son
latencia del montaje. **Sin control en el mismo entorno, una latencia no es un
veredicto.** Y para el anillo de foco, `.focus()` programático NO enciende
`:focus-visible` en Chromium: hay que TABULAR, o se mide el caso que no importa.
> ⚠️ **LECCIÓN DE INSTRUMENTO (2026-08-10), y la más cara de esta pasada.** Las
> mediciones en navegador del 2026-08-09 se hicieron contra un dev server cuyo
> grafo era ANTERIOR a los arreglos ya commiteados: `npm run dev` reutilizó una
> caché de Vite de una ejecución previa. El delator fue aritmético — las
> duraciones medidas (7867 / 3567 ms, ratio 2.21) son EXACTAMENTE las que el
> comentario del arreglo cita como estado anterior («12 500 took 7.9 s while 48
> took 3.6 s, a 2.21 ratio»). **Que el commit esté en HEAD no significa que el
> servidor lo sirva.** Antes de medir percepción: servidor recién arrancado, y
> si un número reproduce con demasiada exactitud el «antes» documentado,
> sospechar del instrumento antes que del código.
> ⚠️ **SEGUNDA LECCIÓN DE INSTRUMENTO (2026-08-10) — el contador de audio no mide
> lo que yo decía.** La sonda parchea `createOscillator` y `createBufferSource` y
> los suma en UN contador. Con eso escribí «N earcons», que es una etiqueta que no
> medí: (a) un earcon sintetizado del catálogo por defecto crea DOS osciladores
> (sine + el intervalo de la voz) y TRES si `roughness` supera el umbral, así que
> el número de nodos no divide por earcon; (b) si un **sound pack** sustituye
> nombres por muestras (`applySoundPack` / `sounds:` en el boot), lo que se crea es
> un `createBufferSource` por reproducción y la aritmética cambia entera; (c) crear
> nodos **no es sonar** — un earcon puede salir a gain 0, y en este proyecto
> «silenciar con gain 0 no es silencio» ya está escrito. Verificado para las rutas
> medidas: `SOUND_CATALOGUE` es síntesis y la galería de blocks **no aplica ningún
> sound pack** (lo que registra `_lib/sema-packs.ts` son packs de sema de
> COMPONENTE — reglas de cascada—, que es otra cosa). Regla: el contador responde
> «¿hubo actividad de audio?» y nada más. Toda fila sobre NIVEL o simultaneidad
> audible se mide offline o se deja sin verificar.
>
> ⚠️ Y el error mayor del que éste es síntoma: **fui a re-medir A-67 sin leer su
> ficha**, que ya traía los gains del catálogo, el empate de `applyDominance` y la
> regla del canon incumplida — todo mejor razonado que mi nota. Presenté como
> hallazgo lo que el registro ya tenía escrito. La primera regla de este fichero
> es unión por `id`: eso incluye LEER la fila antes de tocarla, no sólo escribir
> en la correcta.
Lo medido, por `id`:
| id | qué se midió | resultado |
| --- | --- | --- |
| A-36 | trigger del cajón a 375px | ⚠️ RETIRADA — medida contra código caducado (ver la nota de instrumento abajo). Re-medida con servidor limpio: **ARREGLADA**. |
| A-47 | anillo de foco del `cta`, tabulando de verdad | REPRODUCIDA y cuantificada: el anillo existe (2px, `:focus-visible`) pero el navegador lo compone en `rgb(165,110,212)` sobre `rgb(142,78,198)` = **1.43:1**, contra el mínimo 3:1 de WCAG 1.4.11. En los DOS elementos del block. |
| A-48 | descarte del `banner` | REPRODUCIDA: sólo `contact-activate`; la desaparición de la tira no la expresa nadie. |
| A-53 | las dos acciones del `hero` | REPRODUCIDA: la primaria estampa `contact-activate` (+2 nodos de audio), la secundaria **0 eventos y 0 audio**, en el mismo `Group` y con la misma jerarquía. |
| A-60 | `AutoGrid` del `site-footer` | CONFIRMADA por lectura: `auto-grid.svelte:26` emite `repeat(auto-fill, …)` y el README del canon documenta la elección — las pistas vacías son la consecuencia declarada. |
| A-61 | submit bloqueado de `contact` | REPRODUCIDA: `disabled=true`, `aria-disabled=true`, **no enfocable** (`tabIndex` 0 pero `focus()` no lo alcanza), con `aria-describedby` apuntando a una frase que quien navega con teclado nunca llega a oír. |
| A-62 · A-74 · A-29 | demos de `team`, `feature-split`, `pricing` | VIVAS por lectura: los `IconButton` siguen sin `onclick`/`href`; los `href` siguen apuntando a fragmentos inexistentes; `previewSrc` sigue sin propagar ningún eje. |
| A-65 | CTA de un plan | REPRODUCIDA: un gesto, DOS eventos sobre el mismo nodo — `contact-activate` +293 ms y `commit-select`+`affirm` **+296 ms**, 4 nodos de audio. |
| A-67 | cambiar de pregunta en `faq` | **SIGUE REPRODUCIÉNDOSE** tras los cambios del framework: el segundo gesto emite `emerge-collapse` y `emerge-expand` en el MISMO milisegundo (con un solo gesto la fila sale falsa por construcción — hay que encadenar dos). ⚠️ Lo único que aporta esta re-medición es ESO. El análisis está en la ficha desde la fase 2 y es más completo que esta nota: gains del catálogo, el empate de `applyDominance`, la regla `CANON.md:253-254` que no se aplica, y la precisión de que «sin atenuar» es impreciso (0.08 + 0.06 < 0.15 de un `emerge.strong`: no hay pico). **Leer la ficha, no esta fila.** |
| A-68 · A-69 | contadores de `stats-band` | ⚠️ RETIRADA — las duraciones 7867/5083/3567 ms son las del código ANTERIOR al arreglo. Re-medidas con servidor limpio: 2005 ms cada una, desfase 0 → **A-69 ARREGLADA**. A-68 queda como tensión doctrinal, no como defecto: el canon decidió en A-69 que la banda **aterrice junta**, y escalonar los arranques rompería esa decisión. Necesita firma. |
| A-75 | re-elegir el mismo plan | REPRODUCIDA: el segundo gesto sobre el plan YA elegido re-emite lo idéntico (`contact-activate` + `commit-select`/`affirm`, +4 nodos). |
| A-85 | `aria-label` del trigger del cajón | ARREGLADA 2026-08-11 — no con el `prop-truthy` prescrito sino con la regla de VOCABULARIO (dos clases de precedencia): `aria-label` es NAMING (`ARIA_NAMING_ATTRS`), el compilador marca el plan `consumerWins`, viaja en el bag (nunca `dom.apply`) y `mergeProps` resuelve consumer-first. El espejo manual de `drawer-provider` retirado. |
| A-09 · A-11 · A-19 · A-25 · A-27 · A-28 · A-55 · A-71 · A-84 · A-86 · A-88 | doctrina, por lectura de la fuente que cada una ancla | TODAS VIVAS, sin cambios desde su ficha. |
| A-79 | idioma del `site-footer` | VIVA por lectura (sigue SOSP): el `Select` cambia el régimen de toda la página y no expresa nada distinto de cualquier otro select. No se midió con sonda — el gesto pasa por un `Select` portaleado y la fila es de sospecha, no de defecto medido. |
**Cabo cerrado de paso — los hermanos del `Field`.** El handoff sospechaba que
`password-field`, `search-field` y `textarea` duplicaban el `commit-submit`
dentro de un `Form` copiando el patrón del `Field`. **No lo duplican, y por una
razón que la sospecha no contemplaba**: `search-field`, `number-field` y
`combobox` no declaran el evento en su provider, y `password-field`
(`password-field-provider.svelte.ts:353-356`) y `textarea`
(`textarea-provider.svelte.ts:353-356`) llaman a `e.preventDefault()` ANTES de
estampar, así que el envío implícito del navegador no ocurre y el form nunca
emite su segundo commit.
⚠️ Eso destapa una pregunta distinta, que se anota sin veredicto porque **no se
ha medido**: si `password-field` cancela el envío implícito, un Enter sobre la
contraseña dentro de un `Form` estampa `commit-submit` (intent `neutral`) y el
formulario **no se envía** — que es el segundo defecto descrito en el comentario
del `Field`, no el primero. Necesita un `Form` con `password-field` y una
medición antes de convertirse en fila.
## Fase 7 — contraste doctrinal de los 15 (2026-08-10, 8 de 15 con la rejilla completa)
Encargo del usuario: contrastar cada block con la documentación del sistema **a
nivel de cada componente que compone y en conjunto**, siguiendo la guía de
creación de blocks y **estableciendo la composición como estrategia válida**, y
comprobar que respeta las directrices de eidos, sema, morfo y soma — más
**motion**, que faltaba en la rejilla y detectó el usuario preguntando si había
leído su doctrina. No la había leído: el primer veredicto sobre coreografía se
emitió sin ella y hubo que retirarlo (abajo).
La vara es la documentación vigente, no la lista de hallazgos de 2026-08-01. Los
ids nuevos que salgan de esta pasada continúan la serie desde **A-91**; los
existentes se corrigen en su ficha, nunca se renumeran.
**Rejilla, siete ejes**: (1) componente a componente contra su propio contrato ·
(2) el conjunto contra B-1…B-11 + regla de forma + doctrina de coordinación ·
(3) morfo · (4) sema — incluido **no re-emitir sobre un control que ya emite** ·
(5) soma · (6) eidos · (7) **motion**: §D.11 (la cascada ES `[data-stagger]` +
preset + regla de fundación, modelo CERRADO), §D.13(1) hijos directos, §D.13(2)
ENTER-ONLY y `{#if}` crudo, §2 KNOWN-FRAGILE (firma y stagger sobre un mismo
nodo se pisan y decide el orden de emisión del CSS), B-11 sin keyframes propios.
⚠️ **Veredicto RETIRADO en esta misma pasada**: «los blocks se saltan `Cascade`»
es falso. `data-stagger` **es** el modelo canónico (§D.11: el eje coordinado se
retiró el 2026-06-19 y el modelo final es preset + stagger + la regla de
fundación), y envolver un `AutoGrid` en `<Cascade>` habría roto el índice, porque
§D.13(1) exige hijos DIRECTOS. Los comentarios de los blocks («must stay the
DIRECT child of the `[data-stagger]` grid») estaban citando esa restricción, que
es justo lo que se leyó como síntoma de atajo. Se registra el error, no se borra.
| block | veredicto | hallazgos |
| --- | --- | --- |
| `banner` | **conforme** | A-55 corregida (mecanismo falso, medido) · **A-91** nueva |
| `pricing` | **conforme** | A-86 vive (ejemplo que no compila) · **A-92** nueva |
| `faq` | **conforme** | A-19 vive (typedoc «centered») · A-67 explicada: rango idéntico, el `>` estricto de la dominancia es deliberado |
| `site-header` | **conforme** | sólo A-11, y es de la demo. B-6 ejemplar: `dom.isAtLeast`, nunca `matchMedia` propio |
| `site-footer` | **conforme** | A-60 aclarada: el arreglo es composición (`Grid` con `auto-fit`), camino que nombra el README del propio `AutoGrid` |
| `contact` | **conforme** — el listón del tier | A-61 se reduce: su segunda mitad ya está cerrada por `uix.announce`, y su disposición está medio obsoleta |
| `newsletter` | **conforme** (rejilla, sin medición en navegador) | gemelo de `contact`; sin filas vivas. Su comentario ya recoge el cambio del canon del 06 |
| `hero` · `cta` · `content-section` · `feature-split` | ⚠️ **BARRIDO PARCIAL, no conforme-verificado** | sólo se midió el eje del `{...rest}` y quién estampa `data-stagger`. Limpios EN ESE EJE (`content-section-body` cierra su tipo con `Omit`). Falta la rejilla: contratos de lo que componen · README · motion · sus filas vivas (A-25 · A-53 · A-74) |
| `team` | **conforme** (rejilla completa) | A-88 vive y es ADEMÁS deriva README↔código (el README ancla la fila de enlaces dos veces y el DOM no lo cumple) · A-94 le toca · A-62 es de la demo |
| `testimonials` · `feature-grid` | ⚠️ **BARRIDO PARCIAL** | **A-94** salió de ese barrido. Falta la rejilla y sus filas vivas: A-27 · A-84 |
| `stats-band` | **conforme** | A-09 y A-68 corregidas en su disposición · **A-93** nueva (la costura de `Motion`) |
Los trece restantes, por filas vivas primero: `pricing` · `faq` · `site-header` ·
`site-footer` · `contact` · `newsletter` · `hero` · `team` · `testimonials` ·
`feature-grid` · `feature-split` · `cta` · `content-section`.
### Precisiones de esta fase sobre fichas existentes (2026-08-10)
- **A-67** — la disposición «canon, no block» se confirma, con el mecanismo por fin nombrado: `applyDominance` (`engine.ts:885-892`) mutea una señal SÓLO si hay otra activa de rango **estrictamente mayor**, y `emerge-collapse` / `emerge-expand` tienen rango IDÉNTICO (ninguno evaluable, ninguno con intent). El comentario de `occurrenceRank` declara que el `>` estricto es deliberado, «so an equal-ranked newcomer stays audible». Que suenen los dos ES la conducta que la regla quiere. La pregunta abierta no es por qué no atenúa, sino si un `collapse`+`expand` del MISMO gesto debe contar como una ocurrencia — decisión de doctrina de sema, no defecto del tier.
- **A-88** — se confirma sin re-medir, y con un agravante de documentación: el README del block afirma DOS VECES el anclaje que no ocurre (mapa de composición, fila `MemberLinks`; y §Decisiones, «The links row is pinned to the bottom»). Las dos piezas del mecanismo verificadas por lectura el 2026-08-10: `team-member.svelte` no pasa `height` al `Stack`, y `motion.svelte:119-129` renderiza un div pelado sin `display` ni `height`, así que el `margin-top: auto` reparte cero. El contrafactual medido ya vive en la ficha.
- **A-62** — la atribución de la ficha (es de la DEMO, no del block) se confirma, y el contraste del conjunto añade un dato: la mini-página de `team` es la ÚNICA del corpus con cero anclas; sus ocho hermanas resuelven las navegaciones con `<Link href>`. El README del block justifica con «no stock faces», que no cubre el defecto: lo que falla no son las fotos, son dos navegaciones expresadas como `<button>` sin destino.
- **A-61** — la fila dice que quien navega con teclado «nunca llega al control NI SE TOPA CON LA FRASE que lo explica». La primera mitad sigue siendo cierta (medido: `disabled`, `aria-disabled=true`, no enfocable); **la segunda ya no**: `contact-reason.svelte` anuncia la frase por `uix.announce` en cada cambio de estado, así que la explicación SÍ llega a la tecnología asistiva aunque el control no se alcance. Esa costura entró el 2026-08-06 y la fila se midió el 05. Su disposición además está medio obsoleta: pedía dar `id` al `Text` y pasar `aria-describedby`, y ambas están ya en el código. Lo que queda es sólo la alcanzabilidad del control (`aria-disabled` en vez de `disabled` nativo, a cambio de bloquear el envío en el handler — el esquema no cubre `unverified`).
- **A-60** — la disposición «se arregla en el BLOCK» es correcta y ahora tiene camino: el README del canon `AutoGrid` documenta `auto-fill` como elección deliberada y nombra la salida — `<Grid templateColumns="repeat(auto-fit, minmax(…, 1fr))">`. Es composición con otro primitivo de layout del canon, sin tocar canon y sin CSS propio.
### A-94 — ARREGLADO · `feature-grid · team · testimonials` · doctrina · MEDIA
**Mecanismo** — A-28 no es de un block: es de TRES, y el barrido del tier lo mide. `feature-grid-items.svelte`, `team-members.svelte` y `testimonials-items.svelte` declaran su tipo público como `= AutoGridProps` —toda la superficie del primitivo— y colocan `{...rest}` ANTES de props fijadas a mano (`align`, `width`, y el propio `data-stagger`), así que un consumidor puede escribir `<Testimonials.Items align="start" width="60ch">`, tipar perfecto y no obtener nada. Prometer y descartar. El resto del tier está limpio: `content-section-body` cierra con `Omit<ProseProps,'measure'>` y los dos `Submit` con `Omit<…,'disabled'>`; en esos dos quedan `aria-label`/`aria-describedby` pisados, pero eso es deliberado (el block posee las palabras de sus estados, B-7) aunque el tipo siga exponiéndolos.
**Evidencia** — Barrido de los 114 ficheros del tier cruzando, por componente, el destructuring con los atributos escritos tras el `{...rest}` del tag raíz: 6 candidatos, 3 descartes reales. `feature-grid/types.ts:35` · `team/types.ts:36` · `testimonials/types.ts:24`, los tres `= AutoGridProps`.
**Disposición** — La solución ya está DENTRO del tier: `PricingPlansProps` (`pricing/types.ts:44-52`) declara un tipo cerrado sin rest, precisamente por esto. Cerrar los tres igual, o mover `{...rest}` al final y aceptar que `align`/`width` sean sobreescribibles. Lo único que no vale es prometer y descartar.
**Arreglo (2026-08-10)** — ARREGLADO 2026-08-10 — con una decisión de forma que NO tiene doctrina escrita detrás y se declara como tal. Los tres tipos quedan `Pick<AutoGridProps, 'minChildWidth' | 'columns' | 'gap' | 'children'>`, no un tipo cerrado escrito a mano como `PricingPlansProps`. Motivo: el `Pick` conserva los tipos EXACTOS del canon (incluido `ResponsiveProp`) y no puede quedarse atrás si el primitivo cambia. ⚠️ Y no es teórico: el primer intento los cerró a mano con `columns?: number` y rompió la demo de `team`, que pasa `columns={{ base: 2, md: N }}` — `svelte-check` lo cazó como +1 error sobre la línea base. Queda una tercera forma de tipar sub-parts en el tier (cerrado a mano · `Pick` · `= XProps` entero) sin regla que diga cuál; candidato a doctrina.
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
### A-95 — CONFIRMADO · `banner` · doctrina · MEDIA
**Mecanismo** — Con `affix="top"` la tira fijada TAPA la cabecera pegada, y la navegación del sitio queda inalcanzable mientras el aviso siga visible. Medido en `/blocks/banner/preview?affix=top` a 1280×700 con la página desplazada 800px: la tira ocupa `0..54` y el `<header>` del `site-header` `0..31` —se solapan—, y `document.elementFromPoint` en el centro del header devuelve LA TIRA. El apilamiento en sí es correcto y está firmado: `Affix` monta en el peldaño `--viewport-placement-z` = 150, «encima de un sticky header, debajo de menús y diálogos», y el escenario lo dice («las dos compiten por el mismo borde — por eso el aviso va por encima del cromo»). El escenario ya compensa con `padding-block-start: 3.5rem` en la página, pero eso empuja el CONTENIDO: un sticky con `top: 0` se sigue anclando donde está la tira.
**La solución NO está en el banner, y ahí estaba el error de análisis.** La primera propuesta fue pasar `offset` al `site-header` (que lo expone, en px), y se cayó con una pregunta del usuario: «¿y si tenemos banners a diferentes alturas?». Cualquier número acordado se rompe con `size="lg"`, con el mensaje envolviendo a dos líneas en móvil, o con dos avisos.
**Cómo lo resuelve la referencia** (verificado en su documentación, 2026-08-15): Mantine NO usa `Affix` para esto —su `Affix` es «renders children inside portal at fixed position», para elementos flotantes puntuales tipo scroll-to-top—. Lo resuelve en **`AppShell`**: todas las secciones son `position: fixed`, las alturas se DECLARAN una vez en el shell (`header={{ height: 60 }}`, aceptando objeto con breakpoints) y `AppShell.Main` es «statically positioned and **offset by the other sections**». Su configuración lleva además `collapsed` —«the section is hidden from the viewport and **doesn't affect the Main offset**»— y `offset?: boolean`, así que un aviso descartado deja de contar para el desplazamiento sin que nadie recalcule nada.
**Evidencia** — `web/routes/blocks/banner/BannerSite.svelte:39-49` (el padding compensatorio) · `src/uix/eidos/components/affix/types.ts` (el peldaño 150) · https://mantine.dev/core/affix/ y https://mantine.dev/core/app-shell/ (leídas).
**Disposición** — **NO se arregla en `banner` ni en `site-header`**: la pieza que falta es la que POSEE las alturas de página y propaga los offsets, y en el plan del tier es el block `app-shell` (F3.1, sin construir). Hasta que exista, cualquier solución en el banner es un número acordado con otro nombre. Lo que sí procede ya, y es documental: que el README del block diga que `affix="top"` no está pensado para convivir con una cabecera pegada — para eso, hasta que haya shell, el app agrupa aviso y cabecera en un solo `Sticky` (⚠️ esa alternativa está RAZONADA, no medida).
### A-96 — CONFIRMADO · `banner` · doctrina · BAJA
**Mecanismo** — `affixOffset` es prop público y no tiene control vivo en la demo; sólo lo tiene `affix`. La regla del tier es «cada prop público, un control vivo» (B-9 + la doctrina de demos del handoff).
**Evidencia** — `web/routes/blocks/banner/+page.svelte` — controles: intent · variant · descartable · affix · dir.
**Disposición** — Un control más, o declararlo en la ficha de la API como omitido a propósito (podría serlo: un selector de longitud CSS es ruido en una fila de chips).
### A-97 — REFUTADO · `feature-grid + 6 blocks` · percepcion · MEDIA
**Mecanismo** — El texto que los blocks pintan con `color="muted"` mide **3,70:1** sobre el lienzo de página en claro, por debajo del suelo AA de cuerpo (4,5:1). Medido hoy en `/blocks/feature-grid/preview`: la descripción de cada celda (`feature-grid-item-text.svelte:12`, `Text color="muted"`, 16px) resuelve tinta `rgb(131,131,131)` sobre fondo `rgb(252,252,252)` = 3,70:1. Es EXACTAMENTE el mismo número que midió el verificador de la fase 2, y ahí está el problema de proceso: ese hallazgo vive enterrado dentro de la ficha de **A-70, que está marcada REFUTADO**. La reclamación de A-70 («feature-grid no tiene nada que auditar perceptivamente») se refutó correctamente, pero el verificador encontró de paso ESTO y nunca se le dio fila propia, así que el recuento del ledger lo daba por inexistente.
**No es de un block: es del tier.** Censo de `color="muted"` en `src/uix/blocks/`: **8 sitios en 7 blocks** — `feature-grid-item-text` (16px, cuerpo) · `feature-split-text` (lg) · `content-section` lede (lg) y meta (sm) · `content-section-media` caption (sm) · `pricing-plan-description` (sm) · `pricing-plan-price` suffix · `testimonials-author-role` (sm). El ratio es idéntico en todos —mismo token, mismo lienzo—; lo que cambia es el umbral aplicable: a 16px y a `sm` es 4,5:1, así que fallan; sólo los `lg` podrían salvarse si superan los 18,66px que bajan el umbral a 3:1.
**Evidencia** — Medición con composición hecha por el navegador (canvas) en `/blocks/feature-grid/preview`, 1280×900, tema claro. ⚠️ La medición en oscuro NO vale: dio el mismo valor, señal de que la preview no conmuta con `prefers-color-scheme`, así que sólo se declara el modo claro.
**Disposición** — El valor sale del token del canon (`content-muted`), no del block, pero es cada block quien ELIGE el rol `muted` para su cuerpo. Dos salidas y no son excluyentes: (a) CANON — que `content-muted` alcance AA sobre el lienzo de página, que es donde se arregla para todos; (b) block-side — no usar `muted` para el CUERPO de un párrafo, reservándolo a metadato. Antes de tocar nada hay que medir los `lg` por separado, porque su umbral es otro.
**REFUTADO el mismo día (2026-08-15) — la vara era ajena.** El proyecto NO mide el contraste con el 4,5:1 genérico de WCAG: su suelo es DOBLE y está escrito en `theming/reference.md:1719` — «floors APCA ≥ 60 ∧ WCAG ≥ 3» — con guard propio (`scripts/contrast-audit.ts`, «WCAG 2 gate + APCA Lc, over the 33 scales × 2 modes»). Y sobre esta tinta en concreto, `reference.md:1780-1781`: «`text·11` is the secondary / low-contrast ink (≈APCA 60) — **marginal sub-4.5:1** on the muddy light-mode scales… **by design**».
Re-medido con las funciones del propio repo (`src/arts/color/apca.ts`, en el formato que declara — gamma sRGB **0..1**, no 0..255): **Lc APCA = 63,7** (suelo 60 ✅) y **WCAG = 3,70:1** (suelo ≥3 ✅). La tinta CUMPLE los dos suelos del sistema.
⚠️ Dos lecciones, y la segunda es de instrumento: (1) **antes de medir contraste hay que usar la vara del proyecto**, que aquí es APCA + un WCAG rebajado para la tinta secundaria, no el 4,5 de manual; (2) el primer intento con `apcaLc` devolvió **102666.5** — Lc va de −108 a +106 — porque le pasé el color en 0..255. Un valor fuera de rango no es un hallazgo raro: es un formato mal pasado.
Lo que SÍ sobrevive de esta ficha, como dato y no como defecto: el censo de dónde se pinta cuerpo con `muted` — 8 sitios en 7 blocks —, útil el día que se retoque ese rol.
### A-99 — CONFIRMADO · `Group` (canon) · doctrina · MEDIA
**Mecanismo** — `Group` **no envuelve, y nadie puede hacer que envuelva**, mientras su documentación promete lo contrario en tres sitios. La cadena, leída entera:
```
group.svelte → <Flex {...restProps} {direction} gap class data-*> ← NUNCA pasa `wrap`
flex.svelte → wrap = 'nowrap' → pushStyleVar('--flex-wrap', …)
flex.css:51 → flex-wrap: var(--flex-wrap, nowrap)
types.ts:4 → GroupProps = Omit<FlexProps, 'wrap'> ← el consumidor tampoco puede
group.css → sólo `--group-attached-overlap`, `data-grow` y las reglas de `attached`
```
Lo que su README afirma: «`wrap="wrap"` + `align="center"` por defecto», «**Defaults pensados para action rows** — `align: center`, `wrap: wrap`», y en la comparativa «Row con wrap por defecto | UIX: **Sí** | Chakra HStack: No». Medido en la demo del propio componente (`/uix/components/group`): `flex-wrap: **nowrap**`, `flex-direction: row`, `align-items: center`. **El `align` SÍ se cumple**, lo que descarta que el recipe no cargue: falta específicamente el `wrap`.
**Evidencia** — Lectura de los cuatro ficheros de la cadena + medición del componente aislado en su propia demo, no en un block.
**Disposición** — CANON. O `group.svelte` pasa el `wrap` con su default documentado, o el recipe lo declara, o la documentación deja de prometerlo. Nota de tipo: `Omit<FlexProps,'wrap'>` fue deliberado (el wrap era una decisión del componente, no del consumidor) — coherente con un default fijo, incoherente con que ese default no exista.
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
**CORREGIDA (2026-08-17) — dos errores de esta ficha, y una de sus tres salidas es imposible.**
1. ⚠️ **«El `align` SÍ se cumple, lo que descarta que el recipe no cargue» era FALSO.** El `align-items: center` que medí salía de la demo, que lo pasa explícitamente (`web/routes/uix/components/group/+page.svelte:15` inicializa su control a `'center'`); su propio generador de snippet trata `stretch` como el default real (línea 84). `group.css` no declara **ni** `align-items` **ni** `flex-wrap`: no había nada que cargar. La lección se repite — **medir un default en una demo que lo pisa no mide un default.**
2. **El hallazgo infracontaba.** Faltaban los DOS defaults que el README promete, no uno. Y el README estaba rancio en las dos direcciones: negaba `attached` como prop canónico y listaba `grow` como «No — gap conocido», cuando ambos llevan implementados desde hace tiempo. Describía la era del baseline `air`.
3. **La salida «que el recipe lo declare» es arquitectónicamente imposible.** `flex.svelte:21` tiene `wrap = 'nowrap'` como default de JS y SIEMPRE escribe `--flex-wrap` inline vía `pushStyleVar`, así que el fallback de `var(--flex-wrap, nowrap)` no se evalúa nunca. Un recipe sólo ganaría con una declaración dura, que congelaría el eje. (Para `align` sí sería viable — `pushStyleVar` ignora `undefined` — pero mezclar las dos vías por el mismo par de defaults sería incoherente.)
**ARREGLADA (2026-08-17)** — en `group.svelte`, con el mecanismo que el propio fichero ya usaba para `direction` y `effectiveGap`: `align = 'center'` como default destructurado (sobreescribible: `FlexProps.align` no está omitido) y `effectiveWrap` interno, `'wrap'` salvo `attached`, que lo invierte a `'nowrap'` — el recipe cuadra radios interiores y tira de un margen negativo asumiendo UNA línea, así que un segmentado envuelto abriría su segunda línea con esquina plana. Espejo exacto de «`attached` implica `gap=0`».
**El censo que decidió la forma**: 77 de 91 usos de `<Group` ya pasan `align` explícito; de los que no, los tres blocks lo reenvían con default propio y sólo **`ButtonGroup` voltea `Group` a columna** en todo el ecosistema — donde `align: center` habría dejado los botones verticales en dientes de sierra. Por eso la excepción vive en su call site (`align="stretch"` cuando `orientation="vertical"`) y no como default condicional en `Group`: un caso no justifica una abstracción.
**Medido tras el arreglo** (Chromium, `/uix/components/button-group`): grupo horizontal `attached` → `flex-wrap: nowrap` (el segmentado no se parte); grupo vertical → `align-items: stretch`, los tres hijos a 301px = ancho del grupo, sin dientes de sierra.
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
### A-98 — CONFIRMADO · `hero` · percepcion · MEDIA
**Mecanismo** — El cluster de acciones del hero no apila en móvil: `hero.svelte:127` lo compone con `<Group gap={3} align="center">`, y `Group` es `flex-direction: row` / `flex-wrap: nowrap`. Medido a 375×800 en `/blocks/hero/preview`: el grupo mide 327px y sus dos hijos 191 + 124 = **315px sin contar el gap**, en una sola fila, con la etiqueta de la acción secundaria ya truncándose. El block hermano resolvió exactamente esto: `cta.svelte:98-99` usa `<Flex direction={{ base: 'column', sm: 'row' }}>`, y sus dos acciones SÍ apilan a la misma anchura (medido: 279 y 191px, una debajo de otra).
**Evidencia** — Playwright headless, 375×800, tras la puerta de hidratación. ⚠️ La primera medición de esta fila fue INVÁLIDA y se descarta: el selector cogió el primer contenedor con dos botones, que en `hero` es el `Stack` de la copia (anchos 100/327/327/327 = eyebrow · título · descripción · acciones), no el `Group`. El dato bueno viene de seleccionar `[data-group]` con ≥2 hijos interactivos.
**Disposición** — Block-side y por composición pura, con el patrón que el tier ya usa: cambiar el `Group` por `Flex direction={{ base: 'column', sm: 'row' }}`, como `cta`. Estaba anotado en el handoff desde julio («`Group` no apila… `hero` todavía compone sus acciones con `Group`») sin medición; ahora la tiene.
**REESCRITA (2026-08-17) — la causa está una capa más abajo: [A-99].** La disposición anterior («cambiar el `Group` por `Flex direction` responsive, como `cta`») da por hecho que el block eligió mal el componente, y es falso: `Group` ES el componente para una fila de acciones —su README lo llama «defaults pensados para action rows»— y además expone `direction` responsive, así que ni siquiera hacía falta `Flex`. Lo que falla es que `Group` no aplica el `wrap` que documenta (A-99), y por eso las acciones ni envuelven ni apilan.
Alcance del síntoma, medido: `hero` (dos acciones, 315 de 327px a 375px, la segunda etiqueta truncándose — contenido REAL de la demo) y `feature-split` (`.Actions` también compone `Group`; su escenario pone una sola acción, así que el caso no se ejercita — al forzar una segunda, sigue en una línea. ⚠️ el «desborda el viewport» que se afirmó primero era artefacto: el ancho lo puso el texto inventado para el clon).
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
**ARREGLADA (2026-08-17) — sin tocar `hero`**, como predecía la reescritura: cayó con [A-99]. Medido en `/blocks/hero/preview` a 375×800 tras la puerta de hidratación, seleccionando el `[data-group]` con ≥2 hijos interactivos (el selector bueno, no el que invalidó la primera medición): `flex-wrap: wrap` · `align-items: center` · **2 líneas** (tops 416,3 y 472,3) · sin desbordamiento. Y el dato que cierra el defecto: la acción secundaria pasa de **124px truncados a 156,9px**, su etiqueta entera.
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
`cta` se salva por accidente: usa `Flex` a mano, no porque `Group` fuera inadecuado.
**Disposición corregida** — sin arreglo block-side. En cuanto `Group` envuelva, los dos blocks se curan solos. Si se quisiera además APILAR en móvil (que no es lo mismo que envolver), eso sí sería block-side y por props: `direction={{ base: 'column', sm: 'row' }}` sobre el propio `Group`.
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
### A-91 — ARREGLADO · `banner` · doctrina · BAJA
**Mecanismo** — El canon avisa EN SU PROPIO TIPO (`eidos/components/banner/types.ts`, typedoc de `BannerIntent`) de que su eje `intent` «conflates two axes the doctrine separates»: `intent` es el eje EVALUATIVO (los 6 intents) mientras `primary`/`secondary`/`tertiary` son roles de jerarquía que pertenecen a `color`; y de que Banner, al no tener prop `color`, «is the one painted component outside the open colour system — resolving this is a pending decision, not an oversight». El block re-expone ese eje entero vía `Omit<BannerProps,'children'>` y su mapa de composición lo describe como «`intent` (the canonical color roles)», sin registrarlo. Su README registra CINCO huecos del canon (el `role="banner"` no anulable, la tinta de la ✕ con sus medidas, la persistencia del descarte, la animación diferida, el sticky) — la doctrina del tier es que «un hueco del canon se registra, no se falsea», y a éste le falta la fila.
**Evidencia** — `src/uix/eidos/components/banner/types.ts` (typedoc de `BannerIntent`) · `src/uix/blocks/banner/README.md` §Gaps · `src/uix/blocks/banner/types.ts:15`.
**Disposición** — Una fila en los Gaps del block, como candidato a canon: el eje de pintura de `Banner` está fuera del sistema de color abierto y el block lo hereda sin poder corregirlo. Cero código.
### A-92 — CONFIRMADO · `pricing` · doctrina · BAJA
**Mecanismo** — `pricing-plan.svelte` documenta en un comentario una restricción estructural del modelo de cascada que la doctrina de motion NO recoge: «el preset debe ser CSS: el retardo del stagger vive en el `animation-delay` del preset (índice × each), y un driver JS (`spring`) no lo lee — con `spring-pop` aquí todos los planes entraban a la vez». Es decir, **sólo los presets CSS participan del stagger**; un preset de driver JS ignora el índice estructural y colapsa la cascada en una entrada simultánea. `docs/theming/motion.md` §D.13 se titula «Structural constraints of the CSS model» y enumera TRES (estructura de hijos directos · salida ENTER-ONLY · depuración); ésta falta, y es la que más fácilmente se incumple al elegir un preset por su nombre.
**Evidencia** — `src/uix/blocks/pricing/pricing-plan.svelte:19-27` (la restricción, aprendida midiendo) · `docs/theming/motion.md` §2 RFC §D.13 (las tres que sí están).
**Disposición** — Un cuarto punto en §D.13 de `theming/motion.md`. Cero código: la restricción ya se respeta en los seis blocks que estampan `data-stagger`.
### A-93 — CONFIRMADO · `stats-band` · doctrina · BAJA
**Mecanismo** — Reservado: ver la ficha de A-68, cuya disposición corregida nombra la costura que falta en `Motion`. Se registra aquí para que el gap de canon tenga id propio y no viaje escondido dentro de una fila de percepción: **`Motion` no expone su momento «visto»**. Lo guarda en `$state` privado (`motion.svelte:42`) y sólo lo materializa como `data-animation-pending` en el DOM, así que un consumidor que necesite sincronizarse con el revelado tendría que montar un observador propio — exactamente lo que B-6 prohíbe. `Cascade` ya expone el equivalente por contexto (`cascade.svelte`, `effectiveOpen`), así que la pieza existe en el canon; le falta a `Motion`.
**Evidencia** — `src/uix/eidos/components/motion/motion.svelte:42,122-124` · `src/uix/eidos/components/cascade/cascade.svelte:38-40,60-66` · `docs/architecture/blocks.md` B-6.
**Disposición** — CANON: dar a `Motion` una costura para su momento visto (contexto o callback), espejando `Cascade`. Desbloquea A-68 sin que el block viole B-6.
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
**ARREGLADA (2026-08-17)** — `src/uix/eidos/components/motion/context.ts`, calcado de `cascade/context.ts`: `Motion` publica `{ readonly seen }` con un getter sobre su `$state`, y `getMotionContext()` sale por el barrel. Devuelve `undefined` fuera de un `Motion`, así que un consumidor suelto no paga nada. La pieza que faltaba en el canon existe ya en las dos formas de animador — el contenedor (`Cascade.open`) y el de un solo elemento.
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
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## Veredictos re-medidos
_Una ficha por `id` verificado. La reclamación completa vive en el fichero del 2026-08-01._
### A-01 — ARREGLADO · `cta` · doctrina · ALTA
**Mecanismo** — `cta.svelte` suelta el canalón cuando `container === 'full'`, así que la disposición del README pasa a ser cierta.
**Evidencia** — Medido en /blocks/cta/preview: el panel pasa de 992px@x144 (idéntico con `lg` y con `full`) a 1264px@x8 = todo el ancho del documento. De paso, la ruta `preview` y la demo ganan el control vivo de `container`, que no existía.
**Disposición** — README y pestaña Gaps actualizados: la fila pasa de «app-land» a SHIPPED con los números.
### A-02 — ARREGLADO · `hero` · percepcion · ALTA
**Mecanismo** — El block estampa el contexto de inversión (`data-on='dark'`) MÁS la propiedad `color` sobre el racimo de copia, así que lo que la app slotea en `actions` hereda la tinta on-solid. La demo deja de pasar el token a mano, que además era inerte.
**Evidencia** — Contraste de la CTA secundaria sobre el fondo compuesto: **1.61:1 → 6.61:1**. Título y descripción también a 6.61:1. Verificado sin regresión en `split` y `center` (tintas originales intactas) y en modo oscuro. Captura mirada.
**Disposición** — Dos huecos de canon DESCUBIERTOS al arreglarlo y registrados en el README del hero: `[data-on]` no arrastra la propiedad `color`, y `Display` no lee `--color-content-primary` (por eso el título conserva su prop). Además, `muted` invertido = 3.73:1 → la descripción se queda en on-solid pleno.
### A-03 — ARREGLADO · `feature-grid` · doctrina · ALTA
**Mecanismo** — Párrafo «Landmark + headings» añadido, con el elemento que emite, si tiene nombre accesible y qué nivel emite cada parte.
**Evidencia** — Hecho en 6 de los 8 READMEs sin declaración: feature-grid · feature-split · stats-band · team · faq · contact. Pendientes: pricing · testimonials.
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
**Disposición** — El resto se cierra con A-14; la regla mecánica llega en la fase 5. **Llegó** (2026-08-09): `blocks:check` exige `**Landmark + headings**` en todo README de block.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-04 — ARREGLADO · `contact` · doctrina · ALTA
**Mecanismo** — `contact-submit` pasa `aria-label` con la palabra de estado. NO hizo falta tocar soma: el canon ya honra un `aria-label` explícito — la sugerencia original mandaba a un cambio de canon innecesario.
**Evidencia** — Medido en /blocks/contact/preview: `aria-label` pasa de «Enviar» fijo a seguir el estado («Enviar mensaje»), y el botón gana `aria-describedby` apuntando a la razón.
**Disposición** — Queda vivo el hilo de canon de A-42: el `aria-label` incondicional del canon rompe WCAG 2.5.3 para CUALQUIER consumidor con texto propio.
### A-05 — ARREGLADO · `contact` · doctrina · ALTA
**Mecanismo** — Enmendada la frontera dura 1 de `architecture/blocks.md`: `$libs/forms` queda nombrada como puerta sancionada, con el motivo (el canon `Form` no re-exporta `createForm` y soma lo consume igual).
**Evidencia** — `docs/architecture/blocks.md` §Hard boundaries.
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
**Disposición** — La lista blanca mecánica entra en la fase 5 del guard. **Entró** (2026-08-09): `blocks:check` rechaza todo especificador que la frontera dura 1 no nombre, y los arts públicos los DERIVA de `src/arts/*` en vez de listarlos a mano. El tier ya cumplía — cero violaciones al encenderla.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-06 — ARREGLADO · `site-footer` · percepcion · ALTA
**Mecanismo** — La demo deja el `<Form>` MONTADO y pone la confirmación como hermana, en vez de sustituirlo. El swap desmontaba el provider dentro del mismo await que corría el handler, así que el `commit-submit` (`sequence:'post'`) resolvía su target a un nodo desprendido y moría en silencio.
**Evidencia** — Sonda de audio + MutationObserver al nodo del form: antes 0 osciladores y 0 mutaciones `data-event`; ahora **2 osciladores** y `data-event=commit-submit` · `family=commit` · `intent=fulfill`, con el form montado y la confirmación visible.
**Disposición** — Doctrina que merece escribirse: un `sequence:'post'` no sobrevive al desmontaje de su propio provider.
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
### A-07 — ARREGLADO · `faq` · doctrina · MEDIA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — El README §Coordination enumera UNA costura («`value` is a bindable SEAM… A seam is not ownership») y el código expone DOS: `types.ts:28` declara `disabled` y `faq-item.svelte:16` lo reenvía a `Accordion.Item`. Y no es un passthrough genérico: `.Item` destructura exactamente `value, disabled, question, children` sin `...rest`, así que exponer `disabled` fue una elección deliberada que la doctrina del block no recoge.
**Evidencia** — `src/uix/blocks/faq/types.ts:27-28`, `faq-item.svelte:10,16`, `faq/README.md` §Coordination.
**Disposición** — Nombrar `disabled` en §Coordination (quién decide bloquear y por qué no necesita frase: la app pone el motivo en el texto de la pregunta). Fase 3, documental.
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
**Re-medición (2026-08-10)** — La costura que faltaba ya está escrita: `faq/README.md:43-45` nombra `disabled` como segunda costura reenviada y explica por qué no necesita frase propia (la app pone el motivo en el texto de la pregunta) — exactamente la disposición. Cerrada por lectura.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-08 — ARREGLADO · `cta` · doctrina · MEDIA
**Mecanismo** — La descripción usa `measure="narrow"` (54ch tokenizado) en vez del `Box maxWidth="60ch"` fuera de escala.
**Evidencia** — Medido: `max-inline-size` = 719.28px = 54ch con esa tipografía a 20px.
### A-09 — CONFIRMADO · `stats-band` · doctrina · MEDIA
**Mecanismo** — El block pasa los DOS caminos a la vez: `<Metrics.Value value={count} …>` y, dentro, `<CountUp to={count}>`. `metrics-value.svelte:22-30` anuncia `String(next)` — crudo — cuando `value` cambia de verdad, y `CountUp` pinta con `uix.format.numbers`. Con `live` y un `count` que el app mueva, la pantalla dice «12.500» y el anuncio «12500». PRECISIÓN sobre la reclamación: no se manifiesta con un `count` constante (el memo de `metrics-value` solo notifica en cambios reales, no en el init), así que la demo no lo enseña; y el formateo del anuncio es del canon `Metrics`. Lo que es del block es elegir pasar `value` teniendo `children`.
**Evidencia** — `src/uix/blocks/stats-band/stats-band-value.svelte:20-22`, `src/uix/eidos/components/metrics/metrics-value.svelte:15-30`, `metrics.svelte:57-58` (`notifyUpdate` solo con `live`).
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
**Disposición** — ⚠️ **CORREGIDA (2026-08-10, contraste doctrinal).** No pasar `value` **apagaría la función en vez de arreglarla**: el contrato de `Metrics.Value` dice que pasar `value` Y `children` es el uso previsto («when both are present, `children` renders and `value` still drives live detection») y que **`live` REQUIERE `value` para detectar el cambio**. Quitarlo deja la banda sin detección live. El defecto está en el CANON: `metrics-value.svelte:40` anuncia `String(next)` —la cifra cruda— teniendo el runtime de formato disponible, que es el mismo que `CountUp` usa para pintar. Arreglo: que el anuncio use el texto formateado.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-10 — ARREGLADO · `site-header` · doctrina · MEDIA
**Mecanismo** — El block cierra `mobileOpen` cuando la variante ancha toma el relevo, leyendo el breakpoint reactivo del framework (`dom.isAtLeast`), no un `matchMedia` propio (B-6).
**Evidencia** — Medido abriendo el cajón a 500px y redimensionando a 1200px: antes `{drawerState:'open', drawerVisible:false, bodyOverflow:'hidden'}` — página congelada sin salida visible; ahora el cajón se cierra y `bodyOverflow` vuelve a `visible`.
### A-11 — CONFIRMADO · `site-header` · doctrina · MEDIA
**Mecanismo** — Las demos redeclaran a mano uniones que el canon ya exporta, y ya han derivado: `ContainerSize` se copia sin `'xxl'` (el canon lo tiene en `container/types.ts:4`, con token real) y `Breakpoint` se copia como tres valores frente a los seis de `libs/dom/responsive.ts:1`. Consecuencia: el control vivo de la demo no puede alcanzar `xxl` aunque el block lo acepte, y nada rompe a nivel de tipo cuando el canon crece.
**Evidencia** — `web/routes/blocks/site-header/SiteHeaderSite.svelte:25-26` y `web/routes/blocks/hero/HeroSite.svelte:30-31` (cuatro uniones copiadas). El block hace lo correcto: `site-header/types.ts` importa los tipos del canon.
**Disposición** — Importar los tipos del canon en las demos. Fase 3.
### A-12 — ARREGLADO · `feature-grid` · doctrina · MEDIA
**Mecanismo** — Typedoc y demo corregidos a `solid`, que es lo que el código hace y lo que el README razona.
**Evidencia** — `feature-grid/types.ts` y `web/routes/blocks/feature-grid/+page.svelte`.
### A-13 — ARREGLADO · `testimonials` · percepcion · MEDIA
**Mecanismo** — CANON. `Card` prometía toda la superficie de `Box` (`CardProps = Omit<BoxProps,'display'>`) y no componía `Box`: lo no destructurado se derramaba como atributo HTML crudo, así que `height="100%"` era INERTE. No puede componer `Box` (es un `<div>` fijo y una Card interactiva es un `<button>`), así que ahora el tipo dice la verdad —solo el eje de TAMAÑO, que es lo único que se le pedía en todo el repo— y `card.svelte` lo aplica de verdad con los mismos helpers de layout.
**Evidencia** — Medido: las 5 tarjetas de testimonials pasan a 169px iguales y las 3 de pricing a 505px iguales, con `block-size` estampado. Un grep del repo entero encontró UN solo consumidor pasando props de Box a Card, así que acotar el tipo no rompe a nadie: ahora un layout inerte es error de compilación.
**Disposición** — Cierra también A-24.
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
### A-14 — ARREGLADO · `feature-split · pricing · testimonials` · doctrina · MEDIA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — Ninguno de los tres README declara landmark ni jerarquía de encabezados. Es la misma falta que A-03, medida sobre el tier entero: 8 de los 15 READMEs no mencionan «landmark» ni una vez.
**Evidencia** — Grep sobre los 15 `src/uix/blocks/*/README.md`: 0 menciones en contact · faq · feature-grid · feature-split · pricing · stats-band · team · testimonials.
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
**Disposición** — CERRADO en la fase 5 (2026-08-09), y con más alcance del que la fila pedía: la regla mecánica de `blocks:check` exige la declaración en los QUINCE, y al encenderla salieron **7** READMEs sin ella — los tres de esta fila más `banner`, `content-section`, `cta`, `newsletter` y `site-footer`, que mencionaban «landmark» de pasada en una celda de tabla pero no declaraban nada. Escritas las 7 leyendo la fuente de cada block, no la plantilla: `pricing` y `testimonials` son `<section>` pelados sin nombre (la app los nombra por `...rest`); `cta`, `newsletter` y `content-section` se nombran con su propio título por `aria-labelledby`; `site-footer` es un `<footer>` real (`contentinfo` mientras siga colgando de `<body>`); `banner` no puede ni poner ni quitar su landmark porque el canon estampa `role="banner"` después del rest — de ahí los dos banner de la página, que ya estaban en sus Gaps.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-15 — ARREGLADO · `contact` · doctrina · MEDIA
**Mecanismo** — `Contact.Reason` es ahora un nodo ESTABLE con `role="status"` e `id`, y el submit lo referencia con `aria-describedby`. Antes el nodo se destruía y se creaba con su propio texto, que es justo lo que una región viva no puede anunciar.
**Evidencia** — Medido: `role=status`, `id=s1-reason`, `aria-describedby=s1-reason` en el botón, y el texto cambia sin recrear el nodo.
**Disposición** — Cierra también A-43.
### A-16 — ARREGLADO · `contact` · doctrina · MEDIA
**Mecanismo** — El README deja de afirmar que el block «no pinta los errores de los campos» — los pinta, y ahora además son alcanzables (A-44).
**Evidencia** — `contact/README.md` §«What it does NOT do», reescrita.
### A-17 — ARREGLADO · `content-section` · doctrina · MEDIA
**Mecanismo** — La pestaña API de la demo dice `measure`, que es el default real.
**Evidencia** — `web/routes/blocks/content-section/+page.svelte`.
### A-18 — ARREGLADO · `team` · doctrina · MEDIA
**Mecanismo** — Comentario traducido al inglés.
**Evidencia** — `team/team-member-links.svelte`.
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
### A-19 — ARREGLADO · `faq` · percepcion · BAJA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — La promesa existe y el código no la cumple, pero SOLO cuando el cap muerde — la reclamación acierta en el mecanismo y exagera el alcance.
1. `src/uix/blocks/faq/types.ts:17` promete sin condiciones: «Wraps a `Box` (measure-cap, **centered**)».
2. `src/uix/blocks/faq/faq-header.svelte:12` renderiza `<Box {maxWidth} width="100%" {...rest}>` — sin `marginX="auto"`. El idioma canónico para centrar un Box con tope existe y lo usa un block hermano: `src/uix/blocks/content-section/content-section-media.svelte:48` (`<Box maxWidth={capWidth} marginX="auto">`). `Box` no centra por sí solo: `maxWidth` viaja por `--box-max-width` (`src/uix/eidos/components/box/box.svelte:89`) y el margen es un canal aparte (`--box-margin-inline`, `box.svelte:103` / `box.css:245`).
3. La raíz `src/uix/blocks/faq/faq.svelte:23` apila con `<Stack gap={8} align="stretch">` → `align-items: stretch`. Con `width:100%` explícito y un `max-width` que muerde, el ítem flex se ancla en cross-start (izquierda en LTR, derecha en RTL), no al centro.
DÓNDE FALLA LA RECLAMACIÓN (matiz que baja la severidad, no la anula): con los defaults del propio block NO se desplaza nada. `container='md'` (`faq.svelte:17`) = `--container-width-md` = `--breakpoint-md` = 768px (`src/uix/eidos/generated/base.css:79,140`) e incluye 16px de padding a cada lado (border-box), así que la columna útil es 736px y el tope de 48rem = 768px NUNCA muerde. Medido: cabecera y acordeón con rectángulo idéntico. El defecto es LATENTE y se materializa por dos vías de API documentadas: `container` a `lg|xl|xxl|full` (prop de primera clase en `types.ts:10`), o un `maxWidth` más estrecho en `Faq.Header` (`FaqHeaderProps = BoxProps`, `types.ts:18` — el propio typedoc invita a cambiarlo). En cuanto una de las dos ocurre, el `Box` queda anclado al borde y el typedoc miente.
**Evidencia** — Playwright headless (1440x900) sobre http://127.0.0.1:5201/blocks/faq/preview. Puerta de hidratación: esperado `style#uix-blocks-display`, luego 0 `[data-animation-pending]`, luego ida y vuelta reactivo real (clic en `[data-accordion-trigger]`: aria-expanded false→true). Simulaciones hechas por los MISMOS canales que usan las props (`--box-max-width` en `[data-container]` = `container='xl'`; `--box-max-width` en el Box de la cabecera = `Faq.Header maxWidth`), nunca DOM fabricado. Style inline que Svelte escribe en la cabecera: `--box-width:100%;--box-max-width:48rem` (sin margen).
Rectángulos (left/width/centro-x):
- DEFAULT (container='md'): cabecera 352/736/cx=720 · acordeón 352/736/cx=720 → IDÉNTICOS, cap de 768px inalcanzable (columna útil 736px). Captura: la cabecera se ve centrada sobre el acordeón. Sin defecto visible.
- container='xl': cabecera 96/768/cx=480 · acordeón 96/1248/cx=720 → **240px descentrada**; en captura el h2 y la entradilla cuelgan a la izquierda del acordeón.
- Faq.Header maxWidth=32rem con container='md': cabecera 352/512/cx=608 vs acordeón cx=720 → 112px descentrada.
- RTL (`?dir=rtl`) + container='xl': cabecera 576/768/cx=960 → se ancla al borde derecho (anclaje lógico, no un bug físico de dirección; sigue descentrada).
- Hipótesis de arreglo, `--box-margin-inline:auto` sobre la cabecera con container='xl': 336/768/cx=720 → centrada, coincide con el acordeón. El arreglo canónico funciona.
Scripts: C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-19.mjs y probe-A-19c.mjs (probe-A-19b.mjs dio una medida inválida del caso xl porque `removeProperty` borró el `--box-max-width` que escribe el propio Box; descartada y rehecha en la c).
**Disposición** — En el BLOCK, no en el canon (`Box` y `Stack` se comportan exactamente como especifican). Dos salidas, una línea cada una:
(a) cumplir el typedoc: `src/uix/blocks/faq/faq-header.svelte:12` → `<Box {maxWidth} width="100%" marginX="auto" {...rest}>`, el mismo idioma que `src/uix/blocks/content-section/content-section-media.svelte:48`. Verificado en navegador que restaura cx=720 con `container='xl'`; con el default no cambia nada (el tope no muerde).
(b) o retirar la promesa de `src/uix/blocks/faq/types.ts:17` («measure-cap, centered» → «measure-cap»), y entonces conviene decir además que con `container='md'` el tope de 48rem es inerte.
Preferible (a): la asimetría con `container='xl'` es fea y el hermano ya la resuelve así. Severidad BAJA confirmada — nada visible en el block tal como se envía y se demuestra.
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
**Arreglo (2026-08-10)** — ARREGLADO 2026-08-10 y MEDIDO. `faq-header.svelte:12` pasa `marginX="auto"` — el mismo idioma que esta ficha ya nombraba en el block hermano (`content-section-media.svelte`). Medido en `/blocks/faq/preview`: el `Box` de `max-width: 768px` resuelve `margin-inline: 248px / 248px`, donde antes daba `0px / 0px` y la caja se anclaba en cross-start. Con los defaults del block el cap no muerde, así que nada se mueve hasta que un consumidor ensancha la sección — que es el caso para el que la promesa estaba escrita. El typedoc «centered» pasa a ser cierto en vez de tener que corregirse.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-20 — ARREGLADO · `faq` · doctrina · BAJA
**Mecanismo** — El README de `faq` declara landmark y jerarquía, y dice explícitamente que el nivel de las preguntas está FIJO en 3 porque `.Item` no reenvía `level`.
**Evidencia** — `faq/README.md`.
**Disposición** — Reenviar `level` queda como gap declarado, no silenciado.
### A-21 — ARREGLADO · `stats-band` · doctrina · BAJA
**Mecanismo** — El typedoc dice `formatStyle`, que es el nombre real; `style` está excluido de `CountUpProps`.
**Evidencia** — `stats-band/types.ts`.
### A-22 — ARREGLADO · `stats-band` · doctrina · BAJA
**Mecanismo** — README de `stats-band` con su párrafo B-8, declarando que la banda NO es landmark ni entra en el esquema de titulares, y cómo hacerlo si se quiere.
**Evidencia** — `stats-band/README.md`. Cierra también A-78.
### A-23 — ARREGLADO · `pricing` · doctrina · BAJA
**Mecanismo** — `Pricing.Switch` pasa `deselectable={false}` y además ignora una salida vacía, así que una segunda pulsación sobre el segmento activo ya no escribe un periodo que el usuario no eligió.
**Evidencia** — Medido en /blocks/pricing/preview: pulsar «anual» dos veces mantiene anual; pulsar «mensual» dos veces mantiene mensual. Antes la deselección caía en el `?? 'monthly'`.
**Disposición** — Cierra también A-45 y A-64.
### A-24 — ARREGLADO · `pricing` · percepcion · BAJA
**Mecanismo** — Ver A-13.
**Evidencia** — Ver A-13.
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
### A-25 — ARREGLADO · `pricing` · doctrina · BAJA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — La demo documenta una elevación que el código se niega explícitamente a hacer: «`featured` le da acento y elevación» frente al comentario de implementación «The featured tier is NOT elevated: `Card` exposes no elevation prop… Registered as a gap in the README instead of faked with an inline `box-shadow`».
**Evidencia** — `web/routes/blocks/pricing/+page.svelte:53` frente a `src/uix/blocks/pricing/pricing-plan.svelte:5-11` y `pricing/README.md:74`.
**Disposición** — Corregir la demo (acento cromático, sin elevación). Fase 3, trivial.
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
**Re-verificación (2026-08-17)** — ya no se reproduce: no queda ninguna mención de «elevación» ni en `web/routes/blocks/cta/+page.svelte` ni en el README del block.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-26 — REFUTADO · `pricing` · doctrina · BAJA
**Mecanismo** — El hecho es cierto (tres cadenas obligatorias, con un comentario que invoca B-7 en términos absolutos: «a block ships zero words of its own»), pero la consecuencia doctrinal no se sostiene. La enmienda del 2026-07-31 acota la propiedad de las palabras a los estados que el block coordina Y que pueden bloquear o estar en vuelo (`blocks.md` §Coordination: «The rule fires where a section can be blocked or in flight»). En `pricing` no se puede bloquear nada: el periodo cambia precios, no impide acciones. Su posición ya está declarada por escrito en la revisión documental de los 15: coordina por contexto, no necesita máquina ni palabras.
**Evidencia** — `src/uix/blocks/pricing/pricing-switch.svelte:12-16` frente a `docs/architecture/blocks.md` §Coordination y `pricing/README.md` §Coordination.
**Disposición** — No se toca. Lo único cierto y menor es que el comentario cita B-7 en su forma anterior a la enmienda; si se roza el fichero por otro motivo, se acota la redacción.
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
### A-27 — ARREGLADO · `testimonials` · doctrina · BAJA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — El tipo público documenta un default que el componente no usa: `types.ts:26` dice «`variant` defaults to `soft`» y `testimonials-item.svelte:13` destructura `variant = 'outline'` (que además es la decisión razonada del README: el soft neutral es casi invisible en claro).
**Evidencia** — `src/uix/blocks/testimonials/types.ts:26` y `testimonials-item.svelte:13`.
**Disposición** — Corregir el typedoc a `outline`. Fase 3, trivial.
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
### A-28 — ARREGLADO · `testimonials` · doctrina · BAJA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — `TestimonialsItemsProps = AutoGridProps`, así que el tipo promete toda la superficie de `AutoGrid`; pero el componente vuelca `{...rest}` PRIMERO y después fija `align="stretch"` y `width="100%"` a mano, y en Svelte el último gana. Un consumidor que pase `align` o `width` los ve descartados en silencio. `gap`, `columns` y `minChildWidth` sí funcionan porque están destructurados.
**Evidencia** — `src/uix/blocks/testimonials/types.ts:24` y `testimonials-items.svelte:11-19`.
**Disposición** — Mover `{...rest}` detrás de los defaults, o destructurar `align`/`width` con su default. Fase 3.
### A-29 — CONFIRMADO · `pricing · testimonials` · percepcion · BAJA
**Mecanismo** — El defecto es real, aunque la reclamación exagera en una pata y se queda corta en el alcance.
Cadena causal:
1. `web/routes/blocks/pricing/+page.svelte:14` y `web/routes/blocks/testimonials/+page.svelte:12` construyen la URL de la vista previa como literal desnudo (`const previewSrc = '/blocks/pricing/preview'`), sin query string y sin `$derived` — a diferencia de los otros 12 demos, que sí la derivan (`hero/+page.svelte:28`, `cta/+page.svelte:21`, `team/+page.svelte:21`…).
2. `web/routes/blocks/_lib/BlockDemo.svelte:141-152` monta el iframe con `src={previewSrc}` tal cual. El iframe es OTRO documento: ni el `data-theme`/`data-mode` del `[data-blocks-shell]` (`+layout@.svelte:150`) ni el `dir` de `[data-blocks-content]` (`+layout@.svelte:256`) cruzan la frontera.
3. `web/routes/blocks/pricing/preview/+layout@.svelte:12-14` deriva los ejes SOLO de `page.url.searchParams`, con caídas duras a `'light'`, `'ltr'`, `'es'`. Sin params → claro, ltr, es. Siempre.
4. Los ejes de la sección viven como `$state` local en `+layout@.svelte:68-71` y nunca se exponen (ni por contexto ni por prop) a `BlockDemo`, así que ni siquiera un demo bien intencionado podría heredarlos hoy.
Que es OMISIÓN y no diseño lo dice el propio código: `web/routes/blocks/_lib/BootUix.svelte:8-9` documenta el contrato — «The axes arrive as props so the preview can be driven from its URL: `?mode=dark&dir=rtl&lang=en` is what the demo's iframe passes». Ningún demo pasa `mode` ni `lang`, y estos dos no pasan nada. El comentario de `pricing/+page.svelte:12-13` («no demo chrome needed beyond the section's axes») delata la suposición: el autor creía que los ejes de la sección cubrían también la rama estrecha.
Consecuencia que lo hace defecto: pricing y testimonials no tienen snippet `controls` propio, así que NO existe ninguna ruta en la interfaz para ver su rama de 375/768 en oscuro ni en RTL — ni el toggle de la topbar, ni un control local, ni el enlace «abrir ↗» (que reusa el mismo `previewSrc` pelado, `BlockDemo.svelte:136`). Bajo la doctrina de este repo (la superficie de demos ES el producto; las demos son testbeds interactivos) eso deja la rama estrecha sin auditar en dos de los tres ejes.
Dos correcciones a la reclamación:
- La pata «español» es CIERTA como hecho de la URL pero INERTE como defecto: todas las cadenas de `PricingSite.svelte` y `TestimonialsSite.svelte` están escritas a pelo en castellano, y `_lib/blocks-langs.ts:14-48` sólo registra `contact.*`. Poner EN en la topbar tampoco cambia nada en la vista a página completa de estos dos blocks. El defecto real son `mode` y `dir`, no `lang`.
- La atribución se queda corta, no larga: el eje `mode` no viaja en NINGUNO de los 15 demos (grep de `preview?` en `web/routes/blocks`: 0 ocurrencias de `mode=` o `lang=`), y el eje `dir` falta además en `faq/+page.svelte:12` y `content-section/+page.svelte:31`. Medí `hero` como control: con la sección en dark+rtl, su iframe sale `?…&dir=ltr` y `data-theme="base-light"` — su control `dir` es propio del demo («dirección (solo la vista previa)», `hero/+page.svelte:191`), no el eje de la sección. Mal atribuir no refuta; sí mueve dónde se arregla.
Confianza alta: medido, no inferido.
**Evidencia** — Playwright headless contra http://127.0.0.1:5201, script en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-29.mjs (+ probe-A-29b.mjs). Puerta de hidratación respetada en AMBOS documentos (esperé `<style id="uix-blocks-display">`, que lo escribe un $effect de cliente) y con ida y vuelta reactiva probada antes de medir (`roundTrip: {before:'light', after:'dark', reactive:true}` al pulsar el botón de modo).
/blocks/pricing con la topbar en dark + RTL + EN:
- shell: data-theme=dark, data-mode=dark, fondo pintado oklch(0.1776 0 0) (casi negro); [data-blocks-content] dir=rtl.
- vista a página completa (mismo documento): section direction=rtl, color oklch(0.9491 0 0) (texto claro). Los ejes SÍ mandan aquí.
- tras pulsar «375»: iframe src = "/blocks/pricing/preview", location.search = "" (vacío). Dentro: wrapper data-theme="base-light", data-mode="light", dir="ltr", section computed direction="ltr", color oklch(0.2435 0 0) (texto oscuro) y fondo REALMENTE PINTADO bajo la section = oklch(0.9911 0 0) (casi blanco), medido subiendo el árbol hasta el primer fondo no transparente. Es decir: documento blanco LTR incrustado en una página negra RTL. Con «768» idéntico: src = "/blocks/pricing/preview".
- /blocks/testimonials: cifras idénticas (base-light, ltr, backdrop oklch(0.9911 0 0), section color oklch(0.2435 0 0), search vacío en 375 y 768).
Descarte de rescates:
- Contexto con colorScheme:'dark' (prefersDark=true verificado en la página) sobre /blocks/pricing/preview SIN params → sigue base-light, color oklch(0.2435 0 0). El esquema del SO no salva la vista.
- La maquinaria existe y funciona: /blocks/pricing/preview?mode=dark&dir=rtl&lang=en → data-theme="base-dark", direction="rtl", color oklch(0.9491 0 0). Sólo falta que el demo construya la URL.
- Control de alcance (hero, que sí deriva la URL): sección en dark+rtl → iframe src ".../hero/preview?…&dir=ltr", interior data-theme="base-light", direction="ltr".
Nota de medición no relacionada con el hallazgo: dentro del iframe quedan nodos [data-animation-pending] que nunca se resuelven (elementos de reveal bajo el pliegue que no llegan a intersectar). No contaminó la medida — la puerta de hidratación interna sí se cumplió (styleTagPresent:true) y el h2 medido tiene geometría real (left 24, width 327 en viewport de 375).
No encaja en ningún falso positivo conocido (F15–F22), ni en «medir antes de hidratar» ni en «pestaña en segundo plano».
**Disposición** — Se arregla en la DEMO, no en el block ni en el canon — y en el sitio compartido, no en los dos ficheros señalados.
1. `web/routes/blocks/+layout@.svelte:68-71`: exponer los ejes (`mode`, `dir`, `language`, `density`) por contexto de sección; hoy son `$state` local sin salida.
2. `web/routes/blocks/_lib/BlockDemo.svelte:141-152` y `:136`: que el shell lea ese contexto y ANEXE los ejes al `previewSrc` que recibe (respetando el `?` o `&` que ya traiga), de modo que el iframe y el enlace «abrir ↗» hereden por defecto. Eso arregla `mode` (y `density`) para los 15 demos de golpe y `dir` para los 4 que no lo construyen (pricing, testimonials, `faq/+page.svelte:12`, `content-section/+page.svelte:31`) sin tocar los 11 que ya lo pasan.
3. Decisión tuya, no mía: si el `dir` local de los demos que sí lo llevan (`hero/+page.svelte:191`, «solo la vista previa») debe seguir existiendo como anulación del eje de sección o desaparecer en favor del eje. Hay solape y conviene resolverlo antes de cablear el punto 2.
4. `density` no viaja tampoco (`BootUix.svelte:30` cae a 'comfortable'); si se toca el punto 2, entra gratis.
5. Candidato a guarda en `blocks:check`: que todo `previewSrc` de un demo pase por el constructor de URL del shell en vez de ser literal.
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
### A-30 — ARREGLADO · `newsletter` · doctrina · ALTA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — La duplicación del verbo es ESTRUCTURAL y está en el morfo, no en el block: `morfo/components/field.ts:29-34` declara `commit-submit` sobre `partRef('input')` con intent **neutral**, y `morfo/components/form.ts:17-22` declara `commit-submit` sobre `partRef('provider')` con intent **fulfill**. Un Enter dentro del input envía el form, así que la composición `Form > Field` dispara los dos: el mismo verbo, dos veces, con intents contradictorios.
**Evidencia** — Lectura de los dos morfos. La medición del auditor (dos earcons a 19 ms) es coherente con la estructura; la parte audible se re-mide en la fase 2.
**Disposición** — Deuda de canon (no es a11y): §6 de `PLAN-blocks-quality.md`. El block no puede evitarlo — no declara ninguno de los dos eventos.
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
**Re-medición (2026-08-10)** — La duplicación estructural está cortada en el origen: `soma/components/field/field-provider.svelte.ts:377` sale con `if (this.provider.form) return;` antes de estampar, y el comentario que lo precede documenta la medición que la fila reclamaba (dos commits a 11 ms con intents contradictorios). Un Enter dentro de un `Form` deja hoy UN solo `commit-submit`, el del form.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-31 — REFUTADO · `newsletter` · doctrina · ALTA
**Mecanismo** — La afirmación —«`signal-warn-invalid` … NUNCA se limpia»— es falsa en la línea que ella misma ancla. `form-provider.svelte.ts:125-129` limpia el target ANTES de decidir si re-emite: `const provider = this.runtime.partRef('provider'); if (provider) this.runtime.clearTarget(provider);`, con el comentario «The clear IS the fix when the form now validates». El estampado `risk` no es indefinido.
**Evidencia** — `src/uix/soma/components/form/form-provider.svelte.ts:120-152`.
**Disposición** — No se arregla. Queda una pregunta distinta y más pequeña, que NO es lo reclamado: la limpieza ocurre en el siguiente submit, no en el instante en que el usuario corrige el campo. Si quieres esa lectura de `untilFix`, es un hallazgo nuevo con su propia fila, no éste.
### A-32 — ARREGLADO · `site-header` · percepcion · ALTA
**Mecanismo** — CANON. El pack seleccionaba la parte `item` mientras el runtime estampaba en el `trigger`: la regla NUNCA casaba y el nav sonaba con la ganancia base de la familia (0.30) en vez de su `commit.subtle` (0.03) — 10× más fuerte de lo diseñado. Ahora morfo, runtime y pack dicen los tres lo mismo: `commit-select`→`link`, `contact-activate`→`trigger`, `emerge-*`→`trigger` (el `content` se monta y desmonta con el panel, así que un estampado dirigido a él muere).
**Evidencia** — Sonda sobre los tres gestos; `morfo:check` verde; suites de sema/morfo/soma 342/343 (el único fallo es un fichero SIN TRACKEAR de otra sesión).
### A-33 — ARREGLADO · `site-header` · percepcion · ALTA
**Mecanismo** — CANON. `openNow` emitía `commit-select`+`affirm` y a `openNow` se llega por HOVER (`scheduleOpen` ← `onpointerenter`): barrer el ratón por la barra anunciaba selecciones que nadie había hecho. Desplegar un panel es una APARICIÓN, así que ahora habla como `emerge-open`/`emerge-close`, la misma lectura que dropdown-menu / popover / tooltip.
**Evidencia** — Sonda, hover sobre el trigger: antes `commit-select`+`affirm`; ahora `emerge-open` · familia=emerge.
### A-34 — ARREGLADO · `site-header` · percepcion · ALTA
**Mecanismo** — Se cae con A-32 y A-33: la inversión de dominancia venía de que el hover sonaba a la ganancia BASE de la familia commit (porque el pack no casaba) mientras el gesto real no sonaba. Ahora el hover es un `emerge` suave, el gesto sobre el trigger tiene su `contact-activate` y el link su `commit-select`.
**Evidencia** — Ver A-32 y A-35.
### A-35 — ARREGLADO · `site-header` · percepcion · ALTA
**Mecanismo** — CANON. `NavigationMenuLinkProvider` no registraba NINGÚN `onclick`, así que navegar —el acto evaluable del componente— era mudo. Ahora el morfo declara `commit-select` sobre la parte `link` (antes sobre `item`) y el provider lo emite en el clic.
**Evidencia** — Sonda `G:/tmp/sonda-sema.mjs`, clic sobre `[data-navigation-menu-link]`: antes 0 eventos y 0 nodos de audio; ahora `commit-select` · familia=commit · intent=affirm sobre el propio link, 2 nodos de audio (un earcon).
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
### A-36 — ARREGLADO · `site-header` · percepcion · ALTA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — La carrera existe y es peor que lo reclamado: el feedback de pulsación del hamburguesa no se trunca, NUNCA se pinta.
Cadena causal:
1. `web/routes/blocks/site-header/SiteHeaderSite.svelte:170-177` compone `<Drawer.Trigger>` con `child` → `<Button {...props}>`. Es la composición canónica (asChild), así que la parte `trigger` del Drawer y el `provider` del Button son EL MISMO nodo DOM.
2. `src/uix/soma/components/drawer/drawer-provider.svelte.ts:424-425` dispara `runtime.trigger('open', { fallbackTarget: e.currentTarget })` — el fallback es INCONDICIONAL.
3. `src/uix/soma/runtime.svelte.ts:757` — `opts.fallbackTarget ?? targetReg?.ref?.current ?? null`: el fallback GANA al target declarado por el morfo (`target: v.partRef('content')`, `src/uix/morfo/components/drawer.ts:22`). Medido: el Content, ya montado y abierto, jamás recibe `data-event` (null). Y con el drawer cerrado el Content ni siquiera está en el DOM (medido), así que el fallback no es un accidente: es la única vía. `emerge.open` se proyecta SIEMPRE sobre el botón.
4. Orden de los dos handlers: `src/uix/soma/components/button/components/button.svelte:47` hace `mergeProps(restProps, state.props)` y `src/uix/soma/props/props.ts:17-24` encadena a=drawer, b=button. El `open` (sequence `'post'`, drawer.ts:30) corre su handler y se suspende en `await tick()`; el `contact-activate` (`'pre'`, `src/uix/morfo/components/button.ts:57-66`) llega a `engine.emit` y estampa síncronamente (`src/uix/sema/engine.ts:405-408` → `src/uix/sema/stamp.ts:26-36`). Un microtask después el `tick()` resuelve y `open` reescribe LOS MISMOS cinco `data-event-*` sobre EL MISMO nodo: los attrs son un namespace compartido sin identidad por señal.
5. Consecuencia CSS: `[data-event-family='contact'][data-event-phase='active'] { animation: press-squeeze }` (`src/uix/eidos/generated/base.css:6531`) es sustituida por `[data-event^='open'][data-event-phase='active'] { animation: present-rise }` (:6493) ANTES del primer paint. El botón hamburguesa no comprime: toca la animación de ENTRADA del overlay sobre sí mismo.
Matiz sobre las cifras de la reclamación (no la refutan, la agravan): el solapamiento medido es de 1.1 ms, no 3 ms, y el factor frente al hold canónico es ~125x, no 40x. Y el `contact` no llega a durar «3 ms visibles»: cero frames pintados.
Efecto secundario del mismo choque: el `awaitExpression` del canal visual (`src/uix/sema/chans/visual.ts:108-131`) lee `target.getAnimations()` del nodo compartido, así que el cleanup del `contact` se queda esperando al `present-rise` ajeno y el desestampado único cae a 374 ms.
**Evidencia** — Playwright headless sobre http://127.0.0.1:5201/blocks/site-header/preview, viewport 420x900 (bajo el breakpoint `md`, con el hamburguesa visible). Puerta de hidratación: espera a `<style id="uix-blocks-display">`, luego a que no queden `[data-animation-pending]`, y después ida y vuelta reactiva REAL (click → `aria-expanded` false→true, Escape → true→false, residuo de attrs = null) antes de medir. Sondas: (a) contador WebAudio parcheando `createOscillator`/`createBufferSource` en `addInitScript` antes de tocar nada (10 osciladores en la sesión — el canal sonoro sí corre, no es el eje); (b) MutationObserver ligado AL NODO concreto del trigger con `attributeOldValue`.
Timeline medido en el hamburguesa (t = ms tras el click):
20.2 data-event null -> contact-activate
20.2 data-event-family null -> contact
20.2 data-event-phase null -> active
21.3 data-event contact-activate -> open <-- +1.1 ms
21.3 data-event-family contact -> emerge
21.3 data-event-id sig-3 -> sig-4
374 data-event open -> null (limpieza única)
Muestreo por rAF de `el.getAnimations()`: el PRIMER frame (t=26.9 ms) ya trae `ev=open fam=emerge` con `present-rise` corriendo; `press-squeeze` no aparece en ninguno de los 60 frames. Segunda sonda con muestreo cada 4 ms durante 2 s: nombres de animación vistos alguna vez sobre el hamburguesa = ['present-rise']. Cero apariciones de press-squeeze.
CONTROL en el mismo documento, Button normal sin drawer (`header a[href="#pricing"][data-button]`, viewport 1280):
0.8 data-event null -> contact-activate
137.4 data-event contact-activate -> null
8 frames consecutivos con `press-squeeze` corriendo (~87 ms de muestras).
Es decir 137 ms de contacto y compresión visible en el botón sano frente a 1.1 ms y cero frames en el hamburguesa.
Montaje del Content: antes del primer open `[data-drawer-content]` NO existe; abierto, existe y su `data-event` es null. Confirma que el fallback no es evitable con un simple `if` y que el target declarado por el morfo nunca se usa.
**Disposición** — CANON, no el block. `site-header.svelte` sólo compone `<Drawer bind:open>` (líneas 58-64) y la demo usa el patrón `child` que el propio CLAUDE.md prescribe; no hay nada que tocar ahí. El arreglo vive en dos sitios posibles, y conviene decidir cuál es el canon:
1. `src/uix/soma/runtime.svelte.ts:757` + `src/uix/soma/components/drawer/drawer-provider.svelte.ts:424-433` — el `fallbackTarget` gana al target del morfo SIEMPRE. Debería ser lo que su nombre dice (`targetReg?.ref?.current ?? opts.fallbackTarget`), y como el Content no está montado al abrir, el `emerge.open` debería aplazarse al tick en que monta (mismo `await tick()` que ya hace la rama `'post'`) para estampar sobre el Content — que es donde el morfo lo declaró y donde `present-rise` tiene sentido.
2. `src/uix/sema/stamp.ts:26-46` + `src/uix/sema/projection/dom.ts` — la proyección es un namespace plano sin identidad: dos señales sobre un mismo nodo se pisan y el cleanup de una borra los tokens de la otra. Mientras eso siga así, cualquier composición asChild de dos partes con evento propio sobre el mismo elemento reproduce el defecto (el Drawer es sólo el caso que la demo expone).
Colateral a arreglar en el mismo paso: `awaitExpression` (`src/uix/sema/chans/visual.ts:108-131`) mide animaciones ajenas del nodo compartido y alarga el hold de una señal por culpa de otra.
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
**Re-medición (2026-08-10, servidor limpio)** — YA NO SE REPRODUCE contra el código actual. Traza sobre el mismo nodo con servidor limpio: `contact-activate` estampado a +207,8 ms del clic y borrado a +410,2 — **202 ms de vida, por encima de su hold `glimpse` de 120 ms**, y el `emerge/open` NO aparece sobre el trigger. La carrera desapareció porque el evento ya no viaja a esa parte (`allowedTargets`, M1(i) `1c4303b10`). Queda vivo, pero como riesgo estructural sin manifestación conocida, que `unstampEventAttrs` borre ignorando la señal: si dos señales vuelven a coincidir en un nodo, el cleanup de una apagará a la otra.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-37 — ARREGLADO · `banner` · doctrina · ALTA
**Mecanismo** — El block pasa `color="currentColor"` al descarte, así que la ✕ hereda la tinta de la tira en vez del `primary` de Button.
**Evidencia** — Medido en los cuatro intents: info 5.93→15.88 · warn 5.93→15.88 · risk 2.05→5.78, igualando la tinta del propio texto del banner (15.88/15.88/5.89). ⚠️ `affirm` EMPEORA (1.98→1.50) y se declara: el texto de esa tira ya está a 3.07:1 (F16) y el camino de color del Button oscurece un 30% el valor explícito, así que `currentColor` no puede reproducir la tinta heredada. Los dos arreglos son de canon y están escritos en el README.
**Disposición** — Declarado, no falseado.
### A-38 — ARREGLADO · `banner` · percepcion · ALTA
**Mecanismo** — CANON. Tres defectos en `link.css`, los tres medidos: (1) `subtle`/`plain` forzaban `color: inherit` a (0,2,0), que gana a la regla de paleta a (0,1,0), así que la prop `color` era inerte — ahora heredan SOLO mientras no se pida color (`:not([data-color]):not([data-color-custom])`), y `link.svelte` deja de defaultear a `'primary'` para que ese `:not` distinga «no pedí color» de «pedí primary»; (2) el hover clavaba `--color-primary-solid-hover`, así que un link con color se volvía morado bajo el puntero — ahora usa su propia ranura de paleta, añadida al slot `link` del recipe; (3) `:active` a (0,2,0) quedaba tapado por el hover de variante a (0,3,0) y el press no se veía con ratón — ahora va último y a la misma especificidad.
**Evidencia** — Medido en /blocks/site-footer/preview: el enlace legal con `color="var(--color-content-muted)"` pasa de computar EXACTAMENTE la tinta del padre a `oklab(0.42529 0 0)`, y el hover a `oklab(0.5628…)` en vez de irse a primary. Suites del canon: 7 fallos = la línea base, sin regresión.
**Disposición** — Cierra también A-58 y A-59, y es la mitad de canon de A-02.
### A-39 — ARREGLADO · `newsletter` · doctrina · ALTA
**Mecanismo** — El ejemplo de `newsletter/index.ts` enseña el handler en `createForm`, que es lo que el tipo ya documentaba.
**Evidencia** — `newsletter/index.ts`.
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
### A-40 — ARREGLADO · `contact` · percepcion · ALTA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — El hecho es exacto y el defecto se sostiene, pero el origen es CANON, no el block. Cadena: `src/uix/blocks/contact/contact-submit.svelte:21` compone el canon `<Form.Submit>` de eidos. `src/uix/eidos/components/form/form-submit.svelte:27,38` es un wrapper PASSTHROUGH: no pasa el snippet `child` de soma, así que `src/uix/soma/components/form/components/form-submit.svelte:36` cae en su `<button>` nativo y pinta el cromo a mano en `form.css:85-190` en vez de componer el `<Button>` del framework — exactamente el bug de passthrough documentado (dialog-trigger 2026-06-19, card-group-title 2026-06-27) y contrario a la Regla 3 del patrón consumidor de Button. Consecuencia sema: el ÚNICO morfo que declara la familia `contact` para una acción es Button (`src/uix/morfo/components/button.ts:53-66`, `contact-activate` con `sequence:'pre'`, precisamente «para que la señal perceptual aterrice en el momento del clic, antes de cualquier trabajo asíncrono»). El morfo de Form (`src/uix/morfo/components/form.ts:16-25`) solo declara `commit-submit` con `sequence:'post'`, y el proveedor lo dispara DESPUÉS del await: `src/uix/soma/components/form/form-provider.svelte.ts:121-151` hace `await this.form.submit(e)` y solo entonces `runtime.trigger('commit-submit')`. Ese await encadena `onValidSubmit` del block (`src/uix/blocks/contact/contact.svelte:60`) con el `send()` de la demo (`web/routes/blocks/contact/ContactSite.svelte:37`, 900 ms). Resultado: el gesto sobre la acción principal no tiene tick de contacto en NINGÚN canal sema, ni micro-compresión `:active` (form.css solo trae `:hover`, `:focus-visible`, `:disabled`), mientras cualquier otro CTA de la misma galería —con sound/haptic ON por diseño, `web/routes/blocks/_lib/BootUix.svelte:62`— sí suena al pulsarse. Matiz honesto que la reclamación no dice: el botón NO queda mudo del todo — a ~45 ms del clic pasa a `disabled` + `data-pending` y reetiqueta a «Enviando…», o sea hay acuse de recibo en el canal de ESTADO; el agujero es el momento de contacto de sema (doctrina cap. 22 §8: «Contact inicia. Commit resuelve.»), y la asimetría con el resto de acciones de la página es perceptible.
**Evidencia** — Playwright headless (scratchpad/probe-A-40.mjs y probe-A-40b.mjs) sobre http://127.0.0.1:5201/blocks/contact/preview?challenge=off. Puerta de hidratación: espera a `style#uix-blocks-display` + ida y vuelta reactiva real (tecleo con keyboard en name/email/message → submit pasa de disabled:true a disabled:false) + espera a 0 `[data-animation-pending]`. Sondas: (a) contador de `createOscillator`/`createBufferSource` parcheado en `BaseAudioContext.prototype` vía addInitScript ANTES de interactuar; (b) MutationObserver atado al NODO del botón y al NODO del form, más red de seguridad en body/subtree filtrando `data-event*`. SUJETO: durante la pulsación (mousedown mantenido 60 ms) → 0 mutaciones, 0 nodos WebAudio, 0 atributos `data-event*` en el botón. Primera mutación a +113,4 ms: `disabled null→""`, `aria-busy false→true`, `data-pending` (estado, no sema). Única emisión sema: `data-event=commit-submit`, `data-event-family=commit`, `data-event-intent=fulfill`, `data-event-phase=active` a +1024,8 ms y SOBRE EL `<form>` (no sobre el botón), sostenida hasta +1429,3 ms; 4 osciladores WebAudio a +1025,5 ms. Etiqueta final «Enviado». Estilo calculado hover vs press idéntico (bg oklab(0.611511 -0.117573 0.046955) = oklch(0.6115 0.1266 158.23), misma color; transform/scale/translate/opacity/filter/box-shadow sin cambio) → tampoco hay respuesta CSS al gesto. CONTROL de sensibilidad de la sonda en /blocks/cta/preview con un `<Button>` canónico: `data-event=contact-activate`, `data-event-family=contact` a +99,9 ms del mousedown (≈40 ms tras el clic), limpiado a +245,3 ms, y 4 osciladores WebAudio. Es decir, la sonda SÍ ve el tick de contacto cuando existe; en el submit de contact no existe. Los 915 ms del auditor son el retardo artificial de la demo (900 ms) + ~110 ms de overhead; yo mido 1024,8 ms.
**Disposición** — CANON, no el block. Arreglo en `src/uix/eidos/components/form/form-submit.svelte`: componer el `<Button>` del framework a través del snippet `child` de soma (Regla 3 del patrón consumidor de Button; `<Form.Submit>{#snippet child({props})}<Button {...props} {variant} {size} {color}>…</Button>{/snippet}</Form.Submit>`), con lo que el submit hereda `contact-activate` (sequence 'pre') en el gesto, el anillo de foco y la micro-compresión de press, y `form.css:85-190` suelta el cromo de acción duplicado. Mismo tratamiento para `form-reset.svelte` y los botones add/remove de auto-fields, que tienen el mismo passthrough. `src/uix/blocks/contact/contact-submit.svelte` no debe tocarse: compone el canon como toca.
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
**Re-medición (2026-08-10)** — MEDIDO de nuevo (Playwright headless, dev server propio en :5201, puerta `#uix-blocks-display` + ida y vuelta reactiva real tecleando los tres campos hasta que el submit se habilita). El submit estampa `data-event=contact-activate`, `data-event-family=contact` **sobre su propio nodo** (`[data-form-submit][data-archetype]`) a +270 ms del `mousedown`; el DOM confirma que compone el canon (`data-button`, `data-archetype=trigger`). CONTROL en el mismo entorno y misma sonda: el `<Button>` canónico de `/blocks/cta/preview` estampa a +299 ms. Es decir, el submit va incluso ANTES que el control — los 270 ms son latencia de mi montaje, no del block, y la asimetría que la fila describía ha desaparecido. Causa: `eidos/components/form/form-submit.svelte:30` compone `<Button>` por el snippet `child`, que es la disposición escrita en esta misma ficha.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-41 — ARREGLADO · `contact` · percepcion · ALTA
**Mecanismo** — CANON. `Field` emitía `commit-submit` en el Enter aunque un `Form` fuese su dueño, y el Enter dentro de un form dispara además la submission implícita del navegador: dos commits del MISMO verbo con intents contradictorios. Peor: con el envío bloqueado el del campo sonaba solo, anunciando que algo quedó fijado cuando no ocurrió nada. Ahora el campo calla cuando tiene `Form` padre —el form es el único que sabe si hubo consecuencia— y sigue hablando cuando está suelto, donde su sello es el único acuse.
**Evidencia** — Sonda `G:/tmp/sonda-sema.mjs enter`: antes 2 eventos a 11 ms (neutral sobre el input + fulfill sobre el form) y 4 nodos de audio; ahora 1 evento (`commit-submit` · fulfill) y 2 nodos. Reproducible por el usuario con un comando.
**Disposición** — Los hermanos que copian el patrón (`password-field`, `search-field`, `textarea`) declaran su propio `commit-submit` y NO están revisados: quedan como cola.
### A-42 — ARREGLADO · `contact` · percepcion · ALTA
**Mecanismo** — CANON, además del arreglo block-side de A-04. `FormSubmitProvider` estampaba `aria-label` SIEMPRE, y el atributo gana al contenido en el cómputo del nombre accesible: cualquier `<Form.Submit>` con texto propio anunciaba «Enviar». Ahora la etiqueta explícita gana, el fallback localizado se aplica solo a un submit SIN contenido, y quien sabe si hay contenido —el componente de soma, que ve `children`/`child`— se lo dice al provider por `hasContent`.
**Evidencia** — Verificado con el AX tree del propio Chrome (CDP) en /blocks/newsletter/preview: `aria-label` = null y el nombre accesible sale de `contents=Suscribirme`. Antes: «Enviar» pisando el texto visible (fallo WCAG 2.5.3 para TODO consumidor). Test del provider actualizado al contrato nuevo; suites en la línea base.
### A-43 — ARREGLADO · `contact` · percepcion · ALTA
**Mecanismo** — Ver A-15.
**Evidencia** — Ver A-15.
### A-44 — ARREGLADO · `contact` · doctrina · ALTA
**Mecanismo** — La máquina separa `incomplete` (algún campo vacío, preguntado a los VALORES) de `invalid` (todo lleno pero algo no valida, preguntado al ESQUEMA), y `canSubmit` deja pasar `invalid` a propósito para que el `Form` del canon marque los campos, enfoque el primero y emita `signal-warn-invalid`.
**Evidencia** — Arco medido en navegador: email roto → botón HABILITADO y «Revisa los campos marcados» → al pulsar aparece `aria-invalid` y el texto de error «Debe ser una dirección de correo válida» → al corregir vuelve a `ready`. `state.test.ts` pasa de 9 a 12 casos.
**Disposición** — Cierra A-87 (duplicado) y la mitad de A-61 (el botón es alcanzable en `invalid`).
### A-45 — ARREGLADO · `pricing` · percepcion · ALTA
**Mecanismo** — Ver A-23.
**Evidencia** — Ver A-23.
### A-46 — REFUTADO · `cta` · percepcion · ALTA
**Mecanismo** — La reclamación tiene dos mitades y la que la convierte en defecto no se sostiene.
1. «no emite ningún evento sema» — HECHO CIERTO, pero es comportamiento de canon, no omisión. `src/uix/morfo/components/link.ts:12-16` declara Link explícitamente pasivo: «Link has hover/active visual states but no semantic events… When Link is used as a button-style affordance (form submit, dialog action), the consumer should wrap a Toolbar.Button instead». En `web/routes/blocks/cta/CtaSite.svelte:72-74` el elemento es un enlace de NAVEGACIÓN (`href="#ventas"`), que es justo el uso canónico de Link; todo enlace del framework es mudo por diseño. Y «no se siente» tampoco es literal: medido en reposo→hover, `link.css:62-69` baja la opacidad a 0.8, `link.css:46-51` saca el subrayado en `:hover` y `:focus-visible`, `link.css:27` da `cursor:pointer` (el párrafo tiene `auto`) y `link.css:75-78` pinta el anillo de foco. Que la secundaria sea más callada que la primaria (que sí es Button+`child` y sí emite) es la jerarquía pretendida: la primaria lleva cromo de botón, así que debe comportarse como botón.
2. «en reposo es tipográficamente idéntica al párrafo de la descripción» — FALSO, medido. El enlace pinta en «Times New Roman» (serif) y el párrafo en «Instrument Sans»: la misma cadena mide 144.4px vs 163.5px. No es idéntica: es una tipografía AJENA.
Ahora bien, esa medición destapa un defecto REAL DISTINTO en la misma ancla, que conviene abrir como hallazgo nuevo (percepción, atribución harness/canon, NO el que reclama A-46): el enlace se pinta en la serif por defecto del UA. `link.css:18` declara `font: inherit` («Inherits the ambient typography», link.svelte:12-13), pero en el arranque de blocks no hay tipografía ambiente — `html` y `body` computan `font-family: "Times New Roman"` porque `web/routes/blocks/_lib/reset.css` sólo resetea el box-model y nadie aplica `--font-family-primary` (`src/uix/eidos/generated/base.css:273`) a la raíz; cada receta se la pone a sí misma (`text.css:17` → `--style-body-font-family`, `button.css:72` → `--button-font-family`). Resultado: en el panel del CTA «Hablar con ventas» sale en serif junto a un botón y un párrafo en sans. Se arreglaría en el harness (`BootUix`/`reset.css` fijando la familia de cuerpo en la raíz) o en canon (que la receta de Link no dependa de una ambiente que la fundación no garantiza).
**Evidencia** — Playwright headless:false sobre http://127.0.0.1:5201/blocks/cta/preview (scripts: scratchpad/probe-A-46.mjs y probe-A-46b.mjs).
PUERTA: esperado `style#uix-blocks-display` (attached) + `waitForFunction` a que no quede ningún `[data-animation-pending]` (0 restantes). Control positivo de hidratación y de sema vivo: click en `a[href="#empezar"]` (el Button con snippet `child`) → contador WebAudio parcheado ANTES de cargar (BaseAudioContext/AudioContext.prototype.createOscillator + createBufferSource) pasa de 0 a **4 osciladores**, y el MutationObserver ligado AL NODO registra la secuencia completa: data-event=contact-activate, data-event-id=sig-0, data-event-phase=active, data-event-family=contact, y su retirada al cerrar el hold.
SECUNDARIA `a[href="#ventas"]`: click real de ratón → osc 0, buf 0, mutaciones 0; atributos tras el click sin ningún `data-event*`. Teclado real (`focus()` + `keyboard.press('Enter')`) → delta osc 0, buf 0, mutaciones 0. Muda: confirmado como hecho.
TIPOGRAFÍA EN REPOSO (sin click, sin hover, sin foco; ratón aparcado en 5,5):
- enlace: font-family «Times New Roman», 20px, peso 400, rgb(255,255,255), text-decoration none, cursor pointer, rect 144.4×27.
- párrafo descripción: font-family «Instrument Sans», «Instrument Sans Fallback», system-ui, sans-serif; 20px, peso 400, rgb(255,255,255), cursor auto, display block.
- primaria: «Instrument Sans…», 20px, peso 500, fondo oklch(0.6406 0.1329 157.68).
- prueba de pintura: canvas measureText('Hablar con ventas') = 144.4px con la fuente computada del enlace vs 163.5px con la del párrafo. document.fonts.check('20px "Instrument Sans"') = true (la webfont SÍ cargó; el enlace no la usa).
- cadena ambiente: los 8 ancestros más `html` y `body` computan todos «Times New Roman».
HOVER medido: opacidad 1 → 0.8 y subrayado; en `:focus-visible`, subrayado + anillo de foco (visible en la captura post-interacción).
Capturas miradas: scratchpad/A-46-rest.png (panel en reposo puro), A-46-zoom.png (el zoom deja ver los serifos de «Hablar con ventas» junto a «Empezar gratis» en sans) y A-46-panel.png (post-interacción, con anillo de foco y subrayado).
### A-47 — CONFIRMADO · `cta` · percepcion · ALTA
**Mecanismo** — CONFIRMADO, pero SOLO en esquema oscuro y con la redacción corregida: el anillo SÍ se pinta y ambos elementos SÍ reciben `:focus-visible` — «quedan sin indicador de foco por teclado» es literalmente falso. Lo real es que el indicador queda por debajo de cualquier umbral de percepción sobre el panel.
Cadena causal:
1. `src/uix/blocks/cta/cta.svelte:111` fija `variant="solid"` y `src/uix/blocks/cta/cta.svelte:39` deja `color = 'primary'` por defecto: los DOS únicos interactivos del block (el `<a>` pintado de Button y el `Link`, `web/routes/blocks/cta/CtaSite.svelte:62` y `:72`) caen sobre un lienzo sólido de la MISMA familia de tono que el anillo.
2. El anillo es de tono primario y translúcido: `src/uix/eidos/generated/base.css:7953` (claro, `color-mix(in srgb, var(--primitive-primary-8) 48%, transparent)`) y `:8669` (oscuro, `--primitive-primary-8` al 52%). Lo consumen `src/uix/eidos/components/button/button.css:115-122`, `src/uix/eidos/components/link/link.css:75-76` y la regla de fundación `src/uix/eidos/archetypes.css:203`.
3. El lienzo sólido NO cambia entre esquemas — medido idéntico, rgb(102,55,144) en claro y en oscuro (`src/uix/eidos/components/surface/surface.css:25-37`). El pigmento del anillo SÍ: en claro `primary-8` es srgb(0.745,0.577,0.894), muy por encima del lienzo; en oscuro baja a srgb(0.518,0.341,0.667), casi la misma claridad que el lienzo. Ahí converge y el anillo se disuelve.
4. `Surface variant='solid'` no re-ancla `--focus-ring-color` para su subárbol, así que el anillo sigue tirando del token global aunque el fondo bajo él ya no sea la superficie de página.
Por qué el defecto se sostiene pese a los matices: en oscuro el anillo mide ΔE-OK 0.051 / 1.25:1 contra el panel, 4.2× menos separación que el MISMO anillo canónico sobre una página oscuro normal (0.214) — no es la línea base del sistema, es un colapso que causa el panel. Y afecta al 100% de la superficie accionable del block.
Por qué la reclamación está a la vez sobredimensionada: en modo CLARO el fenómeno NO existe. El anillo sobre el panel sólido mide ΔE-OK 0.141 / 1.84:1, es decir IGUAL o algo mejor que el anillo canónico sobre la superficie de página ordinaria (0.138 / 1.49:1). El panel no se come nada en claro; la severidad ALTA declarada corresponde más bien a MEDIA (un solo esquema).
**Evidencia** — Sonda Playwright headless propia: C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-47{,b,c,d}.mjs, contra el dev server ya arrancado en 127.0.0.1:5201.
Protocolo: (1) puerta de hidratación esperando `document.getElementById('uix-blocks-display')`; (2) espera a 0 nodos `[data-animation-pending]` — el reveal de Motion sólo lo quita el cliente, así que sirve de ida y vuelta reactivo real; (3) foco por TECLADO real (`page.keyboard.press('Tab')`), nunca `.focus()` programático — verificado `el.matches(':focus-visible') === true` en los 2 elementos; (4) captura de pantalla a deviceScaleFactor 1, decodificada en canvas dentro de la propia página, y muestreo de la COLUMNA de píxeles reales en el centro del borde superior (zona recta, sin el redondeo de esquina), leyendo el compuesto pintado (lienzo + gradiente + alfa del anillo), no el token nominal.
URLs medidas: /blocks/cta/preview · ?mode=dark · ?gradient=false · ?color=teal · ?color=neutral · controles en /blocks/faq/preview (+?mode=dark), /blocks/site-header/preview?mode=dark, /blocks/cta.
Números (bg adyacente → anillo · WCAG · ΔE-OK):
- CTA panel sólido CLARO — «Empezar gratis» rgb(102,55,144)→rgb(144,98,183) · 1.84 · 0.1408 ; «Hablar con ventas» 1.86 · 0.1431
- Línea base CLARO, mismo anillo canónico fuera de panel (faq) rgb(252,252,252)→rgb(222,201,240) · 1.49 · 0.1382
- CTA panel sólido OSCURO — «Empezar gratis» rgb(102,55,144)→rgb(118,70,157) · 1.25 · 0.0508 ; «Hablar con ventas» 1.26 · 0.0530
- Línea base OSCURO, mismo anillo fuera de panel (faq / site-header) rgb(17,17,17)→rgb(77,53,97) · 1.80 · 0.2135
Otros lienzos, claro: gradient=false 1.43 · teal 1.36/1.38 · neutral 1.49/1.52. Oscuro neutral 1.24/1.22. Ningún escalón sólido llega a 3:1.
Estilos calculados: outline `2px solid color(srgb 0.745076 0.576585 0.894185 / 0.48) offset 1px` (claro) y `color(srgb 0.517546 0.341223 0.66659 / 0.52)` (oscuro); `box-shadow: none` en todos. Panel: `background-color: oklch(0.5556 0.1829 305.86)`, `background-image: linear-gradient(oklch(0.507448 …), oklch(0.411144 …))` — idéntico en ambos esquemas.
Umbral aplicado: WCAG 2.2 SC 1.4.11 (no textual / indicador de foco) = 3:1. Ninguna configuración lo alcanza; el hallazgo se sostiene sobre el DELTA contra la línea base del propio sistema, no sobre el umbral absoluto.
**Disposición** — CANON, no el block. El arreglo correcto es que `Surface variant='solid'` re-ancle `--focus-ring-color` para su subárbol en `src/uix/eidos/components/surface/surface.css:25` (junto a donde ya fija la tinta de contraste), apuntándolo a la ranura de contraste del lienzo (`currentColor` / on-solid) en vez de dejar que herede el primario translúcido global de `src/uix/eidos/generated/base.css:7953`/`:8669`. Así se arregla de una vez TODO block que pone acciones sobre lienzo sólido (cta, hero, banner, newsletter…), no sólo éste, y `Button`/`Link`/`archetypes.css:203` lo heredan gratis sin tocar sus recetas — exactamente lo que anticipa el comentario de `button.css:118-120`.
Parche local en el block NO recomendado: el contrato B de cta prohíbe `.css` propio (`src/uix/blocks/cta/cta.svelte:14-16`), así que sólo cabría un `style` en línea sobre el `Surface` de `cta.svelte:111` — deuda y repetida en cada block hermano.
Al cerrarlo, corregir también la redacción del hallazgo en el ledger: el indicador EXISTE y ambos elementos son alcanzables por teclado; el defecto es de contraste del anillo y sólo en esquema oscuro. Y anotar el hueco de percepción en `src/uix/blocks/cta/README.md`, junto a los que ya declara el docstring (cta.svelte:18-23).
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
**Re-medición y DISPOSICIÓN CORREGIDA (2026-08-17)** — sigue viva y con el mismo número: 1,43:1 compuesto, en los dos elementos del block. Pero la disposición anterior («que `Surface variant='solid'` re-ancle `--focus-ring-color` para su subárbol») es **la capa equivocada, y eso explica el revert de `e468e764b`**.
La doctrina lo fija en `theming/reference.md:1797` (§«Borders — two tiers»): el anillo de foco es «the one always-sole indicator» y está en el tramo **load-bearing con objetivo 3:1** —no entre los decorativos exentos—, PERO su apariencia es «a config axis, parameterised, NOT hardcoded»: `primitives.focusRing` + `color.focus.{ring,ringError}`, emitidos UNA vez como `--focus-ring-*`. Y textualmente: «The shipped default (`primary·8 @ ~50%` translucent) **measures sub-3:1 as a raw ratio**; hardening it (opaque color, `innerWidth > 0`, offset) is a **default-VALUE decision via config, never a per-component CSS change**».
Es decir: el proyecto YA SABE que su anillo por defecto queda sub-3:1, y tiene decidido dónde se arregla — subiendo el valor por defecto en la config del tema, no re-anclando el token desde un componente. El intento revertido hacía justo lo segundo.
**Disposición** — decisión de VALOR sobre `color.focus.ring` / `primitives.focusRing`, fuera del tier `blocks` y fuera de `Surface`. El block `cta` no tiene nada que arreglar: sólo elige un lienzo sólido, que es donde el default se nota más.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-48 — CONFIRMADO · `banner` · percepcion · MEDIA
**Mecanismo** — La reclamación acierta en el defecto y se equivoca en un detalle del mecanismo; la corrijo.
1. «Sólo se expresa contact-activate» — CIERTO. El único gesto cableado es `src/uix/blocks/banner/banner.svelte:59` (`<Banner.Close onclick={onDismiss} />`). Esa cadena es `Banner.Close` → `IconButton` → `Button` de eidos → `Button` de soma, cuyo morfo declara UN solo evento, `contact-activate` sin intent (`src/uix/morfo/components/button.ts:56-72`). El canon `Banner` declara CERO eventos a propósito (`src/uix/morfo/components/banner.ts:15-28`) y el README del block lo asume («It emits no semantic event… the verb belongs to whoever closes it», `src/uix/blocks/banner/README.md:28-30`). Quien cierra es la app: `web/routes/blocks/banner/BannerSite.svelte:47` hace `dismissed = true` y `:37` lo quita con un `{#if}` — sin emitir nada. Ni `banner.css` ni `banner.svelte` de eidos traen animación, transición ni `@starting-style`, así que la tira desaparece de golpe. Medido a nivel de documento: tras el descarte los ÚNICOS estampados sema de toda la página son los tres `contact-*` del botón. La desaparición, en efecto, no la expresa nadie.
2. «El estampado cae sobre un nodo YA fuera del documento» — FALSO tal cual está escrito. El orden real es el inverso: `mergeProps(restProps, state.props)` (`src/uix/soma/components/button/components/button.svelte:47`) compone con `composeHandlers(a, b)` que ejecuta PRIMERO el del consumidor (`src/uix/soma/props/props.ts:21-24`), o sea `onDismiss`, y DESPUÉS el de soma, que llama `void this.runtime.trigger('contact-activate')` (`src/uix/soma/components/button/button-provider.svelte.ts:134`). Como el flush de Svelte es un microtask, cuando sema proyecta el `data-event-*` el nodo TODAVÍA está en el documento (medido `isConnected: true`, `document.contains: true` en el instante de la escritura). Se desconecta ~11 ms después.
3. La CONSECUENCIA que convierte esto en defecto SÍ se sostiene, y es lo que lo confirma: entre el estampado y la retirada del nodo no llega a pintarse ni un fotograma, así que la reacción visual del canon para la familia contact —`[data-event-family='contact'][data-event-phase='active'] { animation: press-squeeze … }`, `src/uix/eidos/generated/base.css:6531`— NUNCA arranca sobre ese botón. Además el hold del canal visual (~133 ms) se consume entero sobre un nodo desconectado, y al final el desestampado también cae fuera del documento. Resultado neto: el único gesto del block no tiene expresión visual ninguna, ni la pulsación ni la consecuencia.
Atribución: el block hace lo que su doctrina manda (no poseer el verbo), así que no se arregla en `banner.svelte`. Confianza alta: las tres piezas (estampado, no-arranque de la animación, ausencia de cualquier otro evento) están medidas con dos sondas y con un botón de control en la misma página.
**Evidencia** — URL medida: http://127.0.0.1:5201/blocks/banner/preview (servidor ya arrancado; 127.0.0.1). Sondas en C:/Users/dev/AppData/Local/Temp/claude/.../scratchpad/probe-A-48.mjs y probe-A-48b.mjs (Playwright headless).
Puerta de hidratación: espera a `document.getElementById('uix-blocks-display')` (lo escribe el `$effect` de `web/routes/blocks/_lib/BootUix.svelte:91-97`), luego a `[data-animation-pending].length === 0`, luego 400 ms. Ida y vuelta reactivo verificado: el click hace desaparecer la tira (`bannerGone: true`), o sea que el `{#if}` del cliente reaccionó.
CONTROL (mismo documento, `<Button>` «Empezar gratis», canon real, NO se desmonta):
- estampado: `data-event=contact-activate` / `-phase=active` / `-family=contact` en t=3752.1 ms, quitado en t=3900.2 (hold ≈148 ms).
- `animationstart press-squeeze` en t=3770.5 y `animationend` en t=3900.4. → la reacción visual SÍ arranca.
- WebAudio (prototipo parcheado ANTES de interactuar): createOscillator 0 → 2.
DESCARTE (`[data-banner-close]`, click de ratón real sobre su boundingBox):
- sonda (b) MutationObserver ligado AL NODO: estampado `contact-activate` + `phase=active` + `family=contact`; desestampado 132.6 ms después.
- sonda síncrona (setAttribute parcheado en la instancia): en el instante de la escritura `connectedAtWriteTime: true`, `inDoc: true`, t=3873.9 → REFUTA el «ya fuera del documento». Retirada de la tira observada a t=3885.4 (+11.5 ms). Los `removeAttribute` del final del hold (t=4006.5) sí caen con `connectedAtWriteTime: false`.
- `animationstart` sobre ese nodo: NINGUNO (`closeAnims: []`), frente al 1 del control. → la reacción visual no se pinta.
- muestreo rAF: fotograma t=4529.6 `{conn:true, ev:null}`; siguiente fotograma t=4555 `{conn:false, ev:'contact-activate'}`. No existe ni un fotograma con el nodo conectado y estampado.
- MutationObserver de documento entero (subtree, filtro `data-event*`): tras el descarte, cero estampados fuera del propio botón. Nadie expresa la desaparición.
- WebAudio: 2 → 4 osciladores, es decir el descarte SÍ suena (el harness arranca con `events: { sound: true, haptic: true }`, `BootUix.svelte:62`). Matiz honesto: el canal sonoro sobrevive al desmontaje porque no depende del nodo; el visual no. En una app real el sonido va apagado por defecto, así que allí el gesto quedaría sin ninguna expresión.
Lo que NO pude medir: no probé lector de pantalla ni región viva; no hay `announce` declarado en el morfo de Button, así que la desaparición tampoco se anuncia, pero eso lo infiero del código, no lo medí.
**Disposición** — No en `banner.svelte` del block: su README ya cede el verbo a quien cierra, y ceder es correcto. Dos sitios:
1. DEMO (lo barato y lo que la auditoría de percepción debería exigir): `web/routes/blocks/banner/BannerSite.svelte:47`. El `dismissed = true` es exactamente el punto donde el canon dice que vive el verbo — que emita el cierre (un `commit`/`emerge` con su intent) sobre una superficie que SOBREVIVA al desmontaje, no sobre el botón que se va con la tira. Hoy el escaparate del block enseña la doctrina «el verbo es tuyo» sin ejercerla, que es la peor lectura posible.
2. CANON (opcional, mayor alcance): `Banner` no tiene expresión de salida — `src/uix/eidos/components/banner/banner.css` no trae animación ni `@starting-style`, y el README del block ya lo tiene registrado como hueco «Animated entrance / exit → deferred». Si se levanta ese diferido, la salida de la tira pasa a expresarse sola y el defecto se cierra en origen para todos los consumidores. Nótese que el argumento del diferido («desplazaría la página al cargar») habla de la ENTRADA; la SALIDA no lo hereda.
Lo que no hay que hacer: intentar salvar el `press-squeeze` del botón de descarte retrasando el desmontaje. El gesto correcto no es pintar la pulsación de un botón que se va, es expresar la consecuencia.
### A-49 — REFUTADO · `banner` · percepcion · MEDIA
**Mecanismo** — El fenómeno es real pero la consecuencia que lo convertiría en defecto no se sostiene, y una parte de la reclamación es literalmente falsa.
1. Qué es el CTA. `web/routes/blocks/banner/BannerSite.svelte:56` pinta `<Link href="#novedades" size="sm" color="currentColor">`. `Link` es un primitivo eidos puro: `src/uix/eidos/components/link/link.svelte:80-102` renderiza un `<a data-link>` crudo, sin runtime de morfo ni `runtime.trigger`. Por eso no hay estampa ni sonido: no hay superficie sema que emitir.
2. Eso es doctrina declarada, no un olvido. `src/uix/morfo/components/link.ts:12-16` («Passive on the morfo surface: Link has hover/active visual states but no semantic events. Click / navigation is the consumer's responsibility… cuando Link se usa como affordance de botón, el consumer compone un Toolbar.Button»), `src/uix/eidos/components/link/README.md:80-86` (§Eventos Sema, «Link declara 0 eventos en su morfo»), `README.md:95` (el gap `commit-navigate` está **diferido** con criterio explícito: «añadir cuando 2 capas lo consuman») y `README.md:105-113` («expone estados visuales… pero **no** semantic events: hover y active no son verbos sema»). El block usa `Link` para lo que el canon lo reserva —navegación a un ancla— y donde sí hay un commit (descartar) usa la superficie correcta: `src/uix/blocks/banner/banner.svelte:59` compone `Banner.Close`, que sí estampa y sí suena (medido).
3. La cláusula «ni en hover» describe el comportamiento de TODO el framework, no del banner: ningún componente estampa ni suena al pasar el puntero (el `Button` de control, medido, también da 0/0 en hover), y el README lo dice en la línea 111. Y «completamente muda» es falso: en hover el `<a>` pasa `text-decoration-line: none → underline` y cambia de color; con Tab real gana `outline: solid 2px`. Hay realimentación perceptiva; lo que no hay es canal sema — que es exactamente lo diseñado para un ancla de navegación.
4. Atribución: aunque se quisiera voz para el CTA, el block no puede dársela (contrato B-1, `docs/architecture/blocks.md:119`: sin morfo, sin pack sema); tendría que salir del gap diferido del canon, que hoy está resuelto conscientemente.
Confianza alta: medido con control positivo en la misma página y en el mismo instante.
**Evidencia** — Playwright headless sobre http://127.0.0.1:5201/blocks/banner/preview?intent=primary&variant=solid (scripts: scratchpad/probe-A-49.mjs y probe-A-49b.mjs).
Puerta de hidratación: esperado `#uij`→`<style id="uix-blocks-display">`, luego 0 `[data-animation-pending]`. Ida y vuelta reactivo real al final: clic en el dismiss → `[data-banner]` presente=true → ausente=false (el `{#if !dismissed}` del cliente reaccionó ⇒ hidratado de verdad).
Sonda (a) contador WebAudio: `AudioContext.prototype.createOscillator/createBufferSource` parcheados en `addInitScript` (antes de todo script de la página). Sonda (b) MutationObserver atado AL NODO del `<a>` (attributes+oldValue) + uno global filtrando `data-event*`.
CTA «Ver qué cambia →» (`<a href="#novedades" data-link data-variant=default data-underline=hover data-color-custom>`):
- hover: osc 0, buf 0, mutaciones 0 (nodo) / 0 (documento).
- clic de ratón real: osc 0, buf 0, mutaciones 0/0; navegó (hash = #novedades). Atributos finales: href, data-link, data-variant, data-underline, data-color-custom, style — ni un `data-event*`.
- teclado real: 8 pulsaciones de Tab hasta enfocarlo (activeElement confirmado) + Enter → osc 0, buf 0, mutaciones 0/0.
CONTROL POSITIVO en la misma página y misma sesión (prueba de que ambas sondas funcionan y de que el canal de sonido está ENCENDIDO en esta app):
- `Button` «Empezar gratis» (canon, intent fulfill), clic → estampa `data-event=contact-activate`, `data-event-family=contact`, `data-event-phase=active`, `data-event-id=sig-0` y luego los retira; osc 0→2.
- `Banner.Close` del propio banner, clic → mismo juego de 4 atributos (`sig-1`); osc 2→4.
- El mismo `Button` en HOVER: mutaciones 0, osc +0 ⇒ nadie estampa ni suena en hover.
Perceptivo del CTA (mismo nodo, computed style): reposo `color: oklab(0.773…)` / `text-decoration-line: none`; hover `color: oklch(0.5246 0.1755 305.39)` / `underline`; foco `outline: solid 2px`. Es decir, sí cambia.
Nota honesta (hallazgo DISTINTO, no el mío): midiendo el compuesto real, el fondo pintado bajo el CTA es el `<header data-banner>` en `oklch(0.5556 0.1829 305.86)` = rgb(142,78,198). Contraste del texto en reposo rgb(181,181,181) = **2.53:1** (falla AA de cuerpo) y en hover rgb(131,71,185) = **1.14:1** (el enlace casi desaparece: la regla `[data-link]:hover{color:var(--color-primary-solid-hover)}` de link.css:62-64 ignora `color="currentColor"`). Eso es un defecto de contraste, no de mudez, y pertenece a otro id.
### A-50 — REFUTADO · `site-footer` · percepcion · MEDIA
**Mecanismo** — Los NÚMEROS de la reclamación son exactos (los medí: 24 interactivos, 18 `Link`, y los 3 botones sociales sí emiten sonido), pero la cláusula que la convierte en defecto —«no tiene canal perceptivo para su gesto principal»— es falsa medida en el navegador, y la atribución al block no existe.
1. El block no es el locus. `src/uix/blocks/site-footer/site-footer.svelte` no contiene ni un `Link` (grep: 0 ocurrencias); el ancla `:77` es `<AutoGrid minChildWidth={minColumnWidth} …>`, la rejilla de columnas. Los 18 enlaces los compone la demo: `web/routes/blocks/site-footer/SiteFooterSite.svelte:219` (16 de columna), `:131` y `:134` (2 de la línea legal). Mal atribuir no refuta por sí solo, pero fija dónde habría que mirar.
2. El silencio de `Link` en sema NO es deriva: es contrato escrito. `src/uix/morfo/components/link.ts:11-16` declara literalmente «Passive on the morfo surface: Link has hover/active visual states but no semantic events. Click / navigation is the consumer's responsibility», y `:21` le da `scope: ['eidos']` — sin capa soma no hay `runtime.trigger` que disparar. La misma posición se repite coherente en `src/uix/morfo/components/anchor-nav.ts:25-27` («Passive (0 events, 0 keyboard): the links are NATIVE anchors») y en `src/uix/morfo/components/breadcrumb.ts:8-11` («no events of its own — the items are Links whose native anchor semantics own the interaction»). Documentación y código dicen lo mismo. Contrasta con el caso hermano de `navigation-menu` (ledger :528), que SÍ es defecto porque el pack de sema promete `commit-select` por enlace y el provider nunca lo emite: allí hay contradicción doc-vs-código; aquí no la hay.
3. La cláusula «sin canal perceptivo» es falsa al medirla. El gesto de navegación del `Link` sí tiene canal visual: reposo `opacity: 1` / `text-decoration: none` → hover `opacity: 0.8` + `underline` (`src/uix/eidos/components/link/link.css:46-51` y `:65-69`), y con Tab real (`:focus-visible` verificado) un anillo de 2px (`link.css:75-78`). Según `docs/CANON.md` §7 la robustez es «una lectura que sobrevive a la pérdida de un canal»: el canal visual está.
4. El término de comparación está mal descrito. Los «tres iconos decorativos» son `Button` etiquetados (`aria-label` = Boletín RSS / Foro de la comunidad / Escríbenos, `SiteFooterSite.svelte:144-152`); suenan porque `src/uix/morfo/components/button.ts:52-72` declara `contact-activate`, que es el canon correcto, y porque la galería enciende los canales a propósito (`web/routes/blocks/_lib/BootUix.svelte:62`, `events: { sound: true, haptic: true }`). Su emisión es la referencia, no la anomalía.
RESIDUO MEDIDO que NO rescata la reclamación (y que ella no formula): en el instante del press el `Link` con `variant='subtle'` tiene delta CERO —`:active` matchea, pero `[data-link][data-variant='subtle']:hover` (0,2,1) en `link.css:65-69` gana por especificidad a `[data-link]:active` (0,1,1) en `link.css:71-73`—, mientras el Button comprime a `scale(0.985)`. Es un hecho de CSS del canon, distinto del reclamado (mudez de sema vs iconos que suenan), y aun así no deja el gesto sin canal: la aproximación (hover) y el foco sí responden. Merecería id propio si alguien quiere perseguirlo; confianza alta, medido tres veces.
**Evidencia** — URL medida: http://127.0.0.1:5201/blocks/site-footer/preview (chromium headless, 1440x900, Playwright por ruta absoluta). Puerta de hidratación: esperé `document.getElementById('uix-blocks-display')`; ida y vuelta reactiva real después (clic en `[data-select-trigger]` → `[data-select-content]` montado en el portal = true, Escape); luego esperé a 0 nodos `[data-animation-pending]`.
CENSO (dentro de `<footer>`): 24 interactivos únicos = 18 `[data-link]` + 3 `button` ghost icon-only (aria-label RSS/Foro/Escríbenos) + 1 `[data-form-submit]` + 1 `[data-select-trigger]` + 1 `input`. Los 18/24 de la reclamación son EXACTOS.
SONDA (a) contador WebAudio: parcheé `createOscillator`/`createBufferSource` en `BaseAudioContext/AudioContext/OfflineAudioContext.prototype` vía addInitScript, ANTES de cualquier script de la página. SONDA (b) MutationObserver atado a los NODOS concretos (`attributes`, `subtree`, `attributeOldValue`) más un barrido documento-completo de `data-event*`.
- Clic de ratón sobre `Link` «Características» (href `#Características`): osc=0, buf=0, 0 mutaciones en el nodo, 0 `data-event*` en todo el documento. Hash cambió a `#Caracter%C3%ADsticas`.
- Activación por TECLADO real: Tab hasta el `Link` («Integraciones», `:focus-visible` = true) + Enter: osc=0, buf=0, 0 mutaciones. La mudez es total, ratón y teclado.
- Clic sobre el Button social «Boletín RSS»: osc=2 y la secuencia de stamp `data-event=contact-activate` → `data-event-id=sig-2` → `data-event-phase=active` → `data-event-family=contact`, y su retirada al cerrar el hold. Suena, confirmado.
- Form.Submit «Enviar»: buf=1 + `data-event=signal-warn-invalid`, `data-event-intent=risk` sobre el `<form>`.
VISUALES del `Link` (getComputedStyle sobre el nodo real):
- reposo: color oklch(0.2435 0 0), opacity 1, text-decoration-line none, outline 0px none.
- hover (`:hover` matcheado): opacity 0.8, text-decoration-line underline. → hay delta.
- press (`:active` matcheado = true): color, opacity, text-decoration y transform IDÉNTICOS al hover → delta cero en el instante del clic.
- Tab (`:focus-visible` = true): outline `2px solid color(srgb 0.745076 0.576585 0.894185 / 0.48)` + underline; `--focus-ring-width` = 2px.
VISUALES del Button social, para comparar: reposo bg rgba(0,0,0,0) → hover bg oklch(0.9932 0.0034 325.6) → press `transform: matrix(0.985,0,0,0.985,0,0)` (la microcompresión del libro).
Scripts: C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-50.mjs, probe-A-50b.mjs, probe-A-50c.mjs.
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
### A-51 — ARREGLADO · `newsletter` · doctrina · MEDIA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — El morfo de `Form` declara la parte `Submit` (archetype `action`, `type=submit`) pero sus TRES eventos —`commit-submit`, `signal-warn-invalid`, `commit-reset`— apuntan todos a `partRef('provider')`: ninguno estampa sobre el control. Como `Form.Submit` no es el `Button` del canon, tampoco hereda su `contact-activate`. Pulsar la acción principal no estampa contact propio y el arco perceptivo empieza directamente por el resultado.
**Evidencia** — `src/uix/morfo/components/form.ts:17-22,34-39,56-61` (targets) y `:90-101` (la parte `Submit`).
**Disposición** — Deuda de canon (morfo de `Form`), no del block y no a11y: §6 de `PLAN-blocks-quality.md`. Emparentado con A-30 y A-51 — el arco de `Form` es un hilo, no tres defectos sueltos.
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
**Re-medición (2026-08-10)** — Cerrada con A-40 y por la misma medición: la acción principal del formulario SÍ tiene contact propio en el gesto, porque `Form.Submit` compone el `Button` del canon y hereda su `contact-activate` (`sequence:'pre'`). Los tres eventos del morfo de `Form` siguen apuntando al provider —eso no ha cambiado ni tiene por qué—, pero la consecuencia que hacía de esto un defecto ya no se da.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-52 — REFUTADO · `site-header` · percepcion · MEDIA
**Mecanismo** — El FENÓMENO es cierto y lo medí: cerrar escribiendo la costura no emite nada. Pero la consecuencia que lo convertiría en defecto del block no se sostiene, por tres razones medidas/leídas:
1. El block no cierra nada. `src/uix/blocks/site-header/site-header.svelte:23,58` sólo declara `mobileOpen = $bindable(false)` y lo reenvía a `<Drawer bind:open={mobileOpen}>`: no hay watcher, no hay escritura propia. La escritura muda que la reclamación observa es del APP (`web/routes/blocks/site-header/SiteHeaderSite.svelte:172,177,180`, `onclick={() => (mobileOpen = false)}`), tierra de snippets. Y el propio README fija la posición firmada (README.md:31 «Owns nothing of its own… forwarding is not owning», ratificada en `docs/process/CONTINUE-blocks.md:104` «costura bindable hacia el canon… reenviar no es poseer»).
2. No es «el único camino de cierre» del block. Los snippets de la app se renderizan DENTRO del provider del Drawer: el `mobileTrigger` de la app monta un `<Drawer.Trigger>` real (medido: `data-drawer-trigger`, `aria-haspopup="dialog"`, `aria-controls="uix-drawer-content-s19"`), luego el contexto llega, y `Drawer.Close` — que sí anuncia, vía `dismissWith('cancel')` en `src/uix/soma/components/drawer/drawer-provider.svelte.ts:1258-1261` — es componible dentro de `mobileNav` exactamente igual. Además el block DOCUMENTA los caminos que sí suenan como propios del Drawer compuesto (`web/routes/blocks/site-header/+page.svelte:212-215`, «trae foco atrapado, bloqueo de scroll y descarte propios»), y ambos están vivos (medidos).
3. La emisión del canon está atada al ACTO (`runtime.trigger`), no al estado, y no puede atarse al binding: TODOS los cierres canónicos escriben la MISMA celda (`drawer-provider.svelte.ts:357` `this.opts.open.current = false` dentro de `handleClose`), así que anunciar la escritura duplicaría el disparo en Escape, overlay, `Drawer.Close` y drag. Es doctrina uniforme, no descuido: `dialog-provider.svelte.ts:266,320,412,417,593` tiene la misma forma y ningún provider vigila `open` para emitir. Y el acto del caso documentado («cerrarlo al navegar», `+page.svelte:186`) es NAVEGAR, cuya superficie es muda POR DECISIÓN firmada del canon (`src/uix/morfo/components/link.ts:11-18`: «Passive on the morfo surface… no semantic events»); cuando el afordance sí posee señal (el CTA `Button` del cajón) el acto SÍ se anuncia — lo medí.
Detalle menor pero indicativo de que la reclamación se escribió sin medir: Escape y overlay NO emiten `emerge.close`; emiten el evento polimórfico `close` concretado como `emerge.dismiss` (`DISMISS_CAUSES`, `drawer-provider.svelte.ts:64-68`), con `data-last-action` `dismissed` / `dismissed-outside`.
Residuo real, no defecto del block: el cierre por costura pierde el `data-last-action` que tiñe la salida y el sello `data-event-*`; quien quiera esa señal compone `Drawer.Close` en `mobileNav`. La salida visual se anima igual en los cuatro caminos.
**Evidencia** — Playwright headless (dos pasadas idénticas), script en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-52.mjs, sobre http://127.0.0.1:5201/blocks/site-header/preview?breakpoint=md, viewport 390x844 (rama del cajón). Puerta de hidratación: espera a `style#uix-blocks-display` CON `--density-space-scale` dentro; después, cero `[data-animation-pending]`; ida y vuelta reactiva = clic en el trigger que voltea `aria-expanded` false→true y sella sema en vivo. Sondas instaladas ANTES de interactuar: (a) contador de nodos WebAudio parcheando `BaseAudioContext/AudioContext.prototype.createOscillator|createBufferSource|createGain` vía addInitScript; (b) MutationObserver de documento con `attributeFilter` `data-event*`/`data-last-action`, MÁS un observer ligado AL NODO `[data-drawer-content]` concreto (sobrevive al desmontaje). Sonido y haptic están ON en este boot (`web/routes/blocks/_lib/BootUix.svelte`, `events: { sound: true, haptic: true }`), así que el contador de audio es significativo. Sin errores de consola.
Números (delta por camino):
- Abrir con trigger: 8 osciladores / 12 gains; sellos `data-event=contact-activate` (family `contact`, el Button) y `data-event=open` (family `emerge`, el Drawer) sobre `[data-drawer-trigger]`, borrados a los ~371 ms.
- Escape: 4 osc / 6 gains; sobre `[data-drawer-content]`: `data-last-action='dismissed'`, `data-event='close'`, `data-event-family='emerge'`, `data-event-phase='active'`; sellos borrados a los ~252 ms y `data-state='closed'` ~258 ms DESPUÉS del sello (secuencia `pre`).
- Overlay: 4 osc / 6 gains; idénticos sellos con `data-last-action='dismissed-outside'`.
- Costura (clic en el `Link` «Analytics», `onclick` → `mobileOpen = false`): 0 osc / 0 gains, CERO sellos `data-event*` en TODO el documento, sin `data-last-action`; el contenido pasa a `data-state='closed'` en el mismo tick y se desmonta. El propio `Link` tampoco emite nada (canon: link muda por diseño).
- Costura (CTA `Button` «Empezar gratis», misma escritura de la costura): 4 osc / 6 gains y sello `contact-activate` en el `<a>` del Button — el ACTO sí se anuncia; el cierre del cajón sigue sin `data-last-action`.
- Prueba de contexto: el `<Drawer.Trigger>` declarado en el snippet de la APP rinde con `data-drawer-trigger`, `aria-haspopup='dialog'` y `aria-controls='uix-drawer-content-s19'` — el contexto del provider atraviesa los snippets, luego `Drawer.Close` es componible en `mobileNav`. (No monté un `Drawer.Close` real porque exigiría editar la demo del repo; la inferencia se apoya en esa medición.)
### A-53 — CONFIRMADO · `hero` · percepcion · MEDIA
**Mecanismo** — Las dos acciones del hero son hermanas literales del mismo contenedor: `src/uix/blocks/hero/hero.svelte:103` mete el snippet `actions` en un único `<Group gap={3}>`, y el probe confirma `cta.parentElement === link.parentElement` (`div[data-group]`). Dentro, `web/routes/blocks/hero/HeroSite.svelte:92-98` pinta el primario con `<Button variant="solid" intent="fulfill">` + snippet `child` que renderiza `<a href="#pricing">`, y `HeroSite.svelte:99-106` pinta el secundario con `<Link href="#docs" variant="subtle" size="lg">`. Funcionalmente son EL MISMO acto (dos anclas de navegación por hash); sólo cambia la pintura. Pero el canal perceptivo no es una cuestión de grado, es binario: `src/uix/morfo/components/button.ts:56-72` declara `contact-activate` (`sequence:'pre'`, sin intent), que dispara la firma `press-squeeze` de `src/uix/eidos/generated/base.css:6531` (keyframes en :6427, `scale(0.96)` + `brightness(0.96)`) y, con el sonido encendido en la galería (`web/routes/blocks/_lib/BootUix.svelte:62`, `events:{sound:true,haptic:true}`), dos osciladores arrancados. `src/uix/morfo/components/link.ts:18-33` tiene `scope:['eidos']` y `parts` sin un solo evento — Link es pasivo por diseño (comentario en :11-16). Resultado: el usuario pulsa dos cosas contiguas de la misma fila y el sistema acusa recibo de una y de la otra no. Y no es que el enlace responda «más flojo»: medido, su estado PULSADO es idéntico byte a byte a su estado hover — cero delta. Intenté refutarlo por cuatro vías y ninguna aguanta: (1) «primario/secundario deben diferir» — deben diferir en énfasis, no en presencia/ausencia del acuse de contacto; (2) «es canon, no del block» — mal atribuir no refuta, y además el block tiene salida propia: el idiom `Button + child + <a>` ya lo usa cuatro líneas más arriba (y `web/routes/blocks/cta/CtaSite.svelte:62-67`), así que un `variant="ghost"` con el mismo `child` conservaría la jerarquía visual y devolvería el contacto; (3) «los enlaces no responden al press en ningún sistema» — en ESTE sí: `src/uix/eidos/components/link/link.css:71` declara `[data-link]:active{color:var(--color-primary-active)}`, sólo que está muerta para `variant='subtle'` porque `link.css:65` (`[data-link][data-variant='subtle']:hover`, 0-3-0) gana por especificidad a `:active` (0-2-0) siempre que se pulsa con el puntero, que ya está en hover — o sea, ni siquiera cumple lo que el propio canon se prometió; (4) «los osciladores quizá no suenan» — no medí ganancia en dB, pero `start()` y `connect()` corren y el sonido está activado por configuración, y el eje visual (squeeze 0.96 vs cero) se sostiene solo aunque el audio estuviese mudo.
**Evidencia** — Playwright headless sobre http://127.0.0.1:5201/blocks/hero/preview (script en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-53.mjs y probe-A-53b.mjs). Puerta: esperado `style#uix-blocks-display` (ausente en el HTML del SSR: el fetch crudo da `uix-blocks-display=false`, `data-animation-pending=true`) y luego 0 nodos `[data-animation-pending]` — la desaparición del atributo la escribe el efecto cliente de `motion.svelte:124`, así que es ida y vuelta reactiva real. Localización: `sameParent:true`, padre `div[data-group]`; CTA 205x44 px, enlace 174x27 px. Sondas de sema: (a) contador WebAudio parcheando `createOscillator`/`createBufferSource`/`AudioNode.connect` en addInitScript ANTES de cargar — línea base tras hidratar: osc 0, started 0; (b) MutationObserver atado a CADA NODO. CTA (ratón): osc +2, started +2, connects +6; mutaciones `data-event=contact-activate`, `data-event-id=sig-0`, `data-event-phase=active`, `data-event-family=contact` puestos en t=6051 ms y retirados en t=6178 (ventana de hold ~127 ms); `animationName=press-squeeze`, `transform=matrix(0.960068,…)`. ENLACE (ratón): osc +0, started +0, connects +0, CERO mutaciones, `animationName=none`, `transform=none`; hover `color=oklch(0.2435 0 0) opacity=0.8 underline` y PULSADO `color=oklch(0.2435 0 0) opacity=0.8 underline` — delta nulo. Teclado real (`focus()` + `keyboard.press('Enter')`, no `.click()`): CTA vuelve a estampar el ciclo completo (`sig-1`, t=8059→8194); enlace, 0 mutaciones (sólo el anillo de foco, `outline: 2px solid color(srgb .745 .577 .894/.48)`). Repetido en tres ejes con resultado idéntico: `?layout=split` (defecto), `?layout=background` y `?mode=dark` — en los tres, CTA osc+2/started+2/connects+6 y enlace 0/0/0, con HELD≡hover.
**Disposición** — Se arregla en el BLOCK (la demo): en `web/routes/blocks/hero/HeroSite.svelte:99-106`, envolver la acción secundaria en el mismo idiom que ya usa la primaria cuatro líneas arriba — `<Button variant="ghost" size="lg">` con `{#snippet child({props, content})}<a href="#docs" {...props}>…` — para que el acuse de contacto exista en las dos y la jerarquía siga viviendo en variant/peso, no en presencia/ausencia de firma. Nota aparte, de CANON y no de este hallazgo: `src/uix/eidos/components/link/link.css:71` (`[data-link]:active`) es inerte para `variant='subtle'|'plain'` porque la regla de hover de `link.css:65` gana por especificidad; si se prefiere no tocar el block, ese es el otro sitio donde el enlace podría recuperar su acuse visual de press.
### A-54 — ARREGLADO · `site-header` · doctrina · MEDIA
**Mecanismo** — El intent evaluable deja de estar cargado en el marco: `commit-select`+`affirm` vive ahora en la parte `Link`, que es la que navega, y el despliegue tiene familia propia.
**Evidencia** — Ver A-32.
### A-55 — CONFIRMADO · `banner` · doctrina · MEDIA
**Mecanismo** — Con `onDismiss` hay TRES márgenes automáticos en la misma fila flex y el hueco libre se reparte entre los tres, así que la columna del mensaje deja de estar centrada: `[data-banner]` es `display:flex` (`banner.css:20`); el `Container` que el block monta trae `align='center'` por defecto, que en `container.svelte:57-58` se traduce en `marginLeft:auto` + `marginRight:auto`; y `banner.css:150-151` añade `margin-inline-start:auto` al `[data-banner-close]`. Sin `onDismiss` los dos autos del Container centran bien — de ahí que la afirmación del block solo deje de ser cierta al pasar el descarte.
**Evidencia** — `src/uix/blocks/banner/banner.svelte:41,55-60`, `src/uix/eidos/components/banner/banner.css:20,146-151`, `src/uix/eidos/components/container/container.svelte:26-30,57-58`.
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
**Disposición** — ⚠️ **CORREGIDA (2026-08-10, contraste doctrinal).** El hecho es cierto y ahora está medido: a 1280px, con `onDismiss` el centro del contenedor cae en 619,5 frente al centro de la tira en 640 — **20,5 px de desvío**; sin descarte, 0. Pero **el mecanismo escrito es falso**: el `margin-inline` computado del Container es `0px / 0px`, así que los «tres márgenes automáticos» no existen. La causa es aritmética: el `Banner.Close` es un hermano flex que consume 41,1 px del ancho (1232 → 1190,9), y 41,1/2 = 20,55 = el desvío exacto. Por tanto `align="left"` **no arreglaría nada** — el contenedor ya no se centra a sí mismo. El arreglo real es que el descarte no compita por el ancho (fuera del flujo) o compensarlo con un hueco simétrico al inicio.
**Determinación (2026-08-10)** — **NO es arreglable desde el block con props públicas.** El canon pinta `[data-banner] [data-banner-close] { margin-inline-start: auto }` (`banner.css:150`) sobre una tira que es `display: flex` (`banner.css:20`), así que el `Close` es un hermano EN EL FLUJO y le resta su ancho al `Container` (medido: 41,1 px de 1232 → el mensaje se descentra 20,5 px, la mitad exacta). Sacarlo del flujo es CSS, y un `<style>` propio aquí no tendría justificación bajo D-BLK.2.
Dos salidas, y la primera es una DECISIÓN VISUAL que el block no debe tomar solo:
1. **Mover el `Close` dentro del `Container`** (composición pura, sin tocar canon): el `Container` recupera el ancho completo y el mensaje se centra respecto a la tira, pero el ✕ pasa a alinearse con la COLUMNA de contenido en vez de con el borde de la tira. En pantallas anchas se mete visiblemente hacia dentro. Es coherente con el «en columna por dentro» que el block promete, pero cambia el aspecto.
2. **Canon**: que `Banner` permita sacar el descarte del flujo, o que centre su contenido con independencia del `Close`.
Mientras no se decida, lo que hay que corregir es la PROMESA: el README del block afirma «full-bleed por fuera, en columna por dentro», y con `onDismiss` eso deja de ser cierto.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-56 — REFUTADO · `banner` · doctrina · MEDIA
**Mecanismo** — B-9 prohíbe algo concreto y estrecho: «never inside a padded frame or a scroll box». Ni una cosa ni la otra ocurren. `BlockDemo.svelte:69-73` renderiza `{@render preview()}` FUERA de su `<Section><Container>`, sin envoltorio, y la región de contenido de la galería no aporta padding: `[data-blocks-content] { flex: 1 1 auto; min-inline-size: 0 }`. El raíl del catálogo es un hermano flex que estrecha el viewport disponible en escritorio y desaparece por completo bajo 64rem — estrechar no es enmarcar—, y para el ancho real está la ruta `preview/`, que es un documento propio.
**Evidencia** — `web/routes/blocks/_lib/BlockDemo.svelte:69-73`, `web/routes/blocks/blocks.css:56-65`.
**Disposición** — No se toca.
### A-57 — ARREGLADO · `newsletter` · percepcion · MEDIA
**Mecanismo** — Sin panel, descripción y nota comparten tinta (`secondary`) y la jerarquía la lleva el tamaño, que es lo que el block siempre documentó. Antes la nota caía al `content-primary` del recipe por no llevar prop.
**Evidencia** — Medido: antes nota 15.88:1 vs descripción 3.70:1 (la nota pesaba 4,3× más); ahora **5.77:1 ambas**, las dos sobre AA. Con panel, 5.18:1 ambas.
**Disposición** — Dato de canon registrado en el README: `muted` sobre la superficie de página mide 3.70:1, bajo AA para cuerpo.
### A-58 — ARREGLADO · `site-footer` · doctrina · MEDIA
**Mecanismo** — Ver A-38: la demo del footer ya expresaba la intención correcta y ahora el canon la respeta.
**Evidencia** — Ver A-38.
### A-59 — ARREGLADO · `site-footer` · percepcion · MEDIA
**Mecanismo** — Ver A-38 para la mitad de la tinta (los enlaces ya no heredan la del título). El escalón de TAMAÑO del título de columna queda como está: es decisión de diseño tuya, no defecto medible.
**Evidencia** — Ver A-38.
**Disposición** — Pendiente tu criterio sobre `size='xs'` del título frente a los enlaces `sm`.
### A-60 — CONFIRMADO · `site-footer` · percepcion · MEDIA
**Mecanismo** — La cadena se sostiene entera y la medí en los tres eslabones.
1. `src/uix/eidos/components/auto-grid/auto-grid.svelte:26` emite literalmente `repeat(auto-fill, minmax(MIN, 1fr))`. `auto-fill` NO colapsa las pistas sobrantes: las crea vacías y les reparte su `1fr`.
2. `src/uix/blocks/site-footer/site-footer.svelte:77` mete las columnas en ese `AutoGrid` con `minChildWidth={minColumnWidth}` (default `'7.5rem'`, línea 33) y `columnGap={6}`, dentro de la región `minmax(0, 3fr)` del Grid exterior (líneas 64-67). Esa región mide 714px al calibre `lg`, y a 7.5rem + 24px de hueco caben exactamente 5 pistas — el default está afinado PARA CINCO, como admite el propio comentario de las líneas 73-76 y `types.ts:26`.
3. Con 2 grupos el `gridTemplateColumns` computado sigue siendo cinco pistas de 123.594px, tres de ellas vacías. Las dos columnas se apelotonan en 0..271px de 714 → **62.0% de la región muerto por caja, 64.4% por tinta**. «Casi dos tercios» es exacto, no inflado.
El contrafáctico cierra la atribución: forzando `auto-fit` sobre el MISMO nodo, las tres pistas vacías colapsan a `0px`, las dos columnas pasan a 345px y el borde derecho llega al final de la región (muerto por caja 0%, por tinta 64.4%→33.4%). O sea, `auto-fill` es la causa real, no un chivo expiatorio.
Por qué es DEFECTO y no una decisión: la frase auditada (`README.md:17` «as many as fit, no breakpoints — same code for 2 groups or 5» y el comentario `site-footer.svelte:72` «Fluid by construction») promete que el mismo código sirve para 2 y para 5. Medido, sólo aterriza en 5: muerto 0% con 5 grupos, 20.7% con 4, 41.3% con 3, 62.0% con 2. Y visualmente el destrozo es el desalineado, no el hueco en abstracto: con 2 grupos la región superior termina en x≈745 mientras el `Separator` y la barra inferior sí llegan a x≈1208 — el pie se ve cortado por arriba a la derecha.
La escapatoria existe pero refuerza el hallazgo en vez de refutarlo: subiendo `minColumnWidth` a 15rem se obtiene exactamente el resultado del `auto-fit` (dos pistas de 345px, 0% muerto por caja). Es decir, para 2 grupos hace falta OTRO `minColumnWidth` que para 5 — justo lo contrario de «el mismo código para 2 o para 5».
Descartados los falsos positivos conocidos: no es F15-F22, y no es medición pre-hidratación ni pestaña de fondo (ver evidencia).
Confianza alta: medido en navegador real, con contrafáctico y a tres anchos.
**Evidencia** — Playwright headless (chromium), viewport 1440x900, sobre `http://127.0.0.1:5201/blocks/site-footer/preview?columns=N`. Scripts en `C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-60.mjs` y `probe-A-60b.mjs`.
Puertas cumplidas: esperé `style#uix-blocks-display` (lo escribe un $effect de cliente, no viene en el SSR), hice ida y vuelta reactivo REAL con `page.keyboard.type()` sobre el `input[type=email]` (`hydrated: true` en las 4 pasadas), metí el `<footer>` en viewport para que corriera el `Motion trigger="viewport"` y esperé a que no quedara ningún `[data-animation-pending]`.
Contexto medido: `containerWidth` 1024px, Grid exterior computado `238px 714px`, `columnGap` 24px, región AutoGrid = 714px.
`gridTemplateColumns` computado y ocupación (izq..der relativos a la región de 714px):
- columns=2 → `123.594px ×5` (5 pistas, 3 VACÍAS); cajas 0..123.6 y 147.6..271.2 → **muerto 62.0%** (por tinta 64.4%); muerto sobre el calibre completo del contenedor 44.8%.
- columns=3 → mismas 5 pistas; última caja acaba en 418.8 → muerto 41.3%.
- columns=4 → mismas 5 pistas; última caja acaba en 566.4 → muerto 20.7%.
- columns=5 → última caja acaba en 714.0 → muerto 0.0%.
Contrafáctico sobre el mismo nodo, columns=2:
- `auto-fit` en vez de `auto-fill` → computado `345px 345px 0px 0px 0px`; cajas 0..345 y 369..714 → muerto por caja 0%, por tinta 33.4%.
- `auto-fill` con `minmax(15rem, 1fr)` (el knob `minColumnWidth` que el block ya expone) → computado `345px 345px`, idéntico resultado.
Estabilidad: repetido a 1024 / 1280 / 1920px de viewport con columns=2 → muerto 62.1% / 62.0% / 62.0% (el contenedor está topado a `lg`, así que el defecto no se diluye ensanchando la ventana).
Capturas del `<footer>`: `scratchpad/A-60-cols2.png` (contenido superior acaba en x≈745 con el separador y la barra inferior llegando a x≈1208 — corte visible), `A-60-cols5.png` (alineado hasta el borde), `A-60-cols2-autofit.png` (contrafáctico).
**Disposición** — Se arregla en el BLOCK, con nota al canon.
Block (`src/uix/blocks/site-footer/site-footer.svelte:77`): la región de columnas no puede reservar 5 pistas cuando el app pasa 2. Opciones, por orden de cirugía: (a) que las pistas sobrantes colapsen (comportamiento `auto-fit`); (b) que el Grid exterior de las líneas 64-67 deje de dar `3fr` incondicional a una región cuyo contenido no lo llena. Cualquiera de las dos exige tocar además `README.md:17` y el comentario de la línea 72: la frase «el mismo código para 2 o para 5 grupos» es hoy falsa en el resultado, y si se opta por documentar el knob en vez de arreglar el layout, entonces la frase debe decir que `minColumnWidth` hay que ajustarlo al número de grupos.
Canon (`src/uix/eidos/components/auto-grid/auto-grid.svelte:26`): `auto-fill` está cableado; no hay forma de pedir `auto-fit` desde fuera. Para una rejilla de tarjetas `auto-fill` es el default correcto (dos tarjetas no deben inflarse a media región), así que no lo cambiaría por defecto — pero le falta la palanca. Un `fit?: 'fill' | 'fit'` en `AutoGridProps` es la extensión mínima, y es lo que dejaría al site-footer resolverlo sin recetas propias (contrato B).
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
**Determinación (2026-08-10)** — el camino existe y es el que nombra el README del canon (`<Grid templateColumns="repeat(auto-fit, minmax(…, 1fr))">`, y `GridProps.templateColumns` es `ResponsiveProp<string>`), pero NO es la sustitución trivial que parecía: el prop del block es `minColumnWidth?: AutoGridProps['minChildWidth']`, o sea `ResponsiveProp<LayoutLengthValue>`, así que construir la pista exige resolverlo antes (precedente en el tier: `content-section` resuelve sus tres ejes con `eidos.resolve()`). Coste real: resolver el valor + reescribir el track + comprobar que no se pierde el comportamiento responsive. No se toca a medias.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-61 — CONFIRMADO · `contact` · percepcion · MEDIA
**Mecanismo** — El mecanismo es real y se cierra en tres saltos. (1) `src/uix/blocks/contact/contact-submit.svelte:21` pasa `disabled={!canSubmit(state)}` a `Form.Submit`; `canSubmit` (`src/uix/blocks/contact/state.ts:65-67`) sólo es cierto en `ready`, así que en `incomplete · unverified · verifying · rejected · sending · sent` el prop viaja por `restProps` → `mergeProps` (`src/uix/soma/components/form/components/form-submit.svelte:31,37`) y aterriza como atributo NATIVO `disabled` en el `<button type="submit">`. Un botón con `disabled` nativo no es enfocable ni por Tab ni por `.focus()`: sale del orden de tabulación. Medido. (2) La frase que lo explica (`contact-reason.svelte:24-26`, texto de `CONTACT_REASON` en `state.ts:76-84`) se pinta como un `<span>` estático visible INMEDIATAMENTE DESPUÉS del botón, sin `aria-describedby` desde el botón, sin `role="status"` y sin `aria-live` en ningún ancestro. Es decir: la razón nunca se asocia al control que explica ni se anuncia cuando cambia. Quien recorre el formulario con Tab (modo formularios de un lector de pantalla) va `nombre → correo → mensaje → enlace hola@vicen.dev`: ni toca el submit ni oye la frase. (3) El golpe más duro sale del MISMO mecanismo y lo medí aparte: con el botón enfocado y `ready`, pulsar Enter lo deshabilita al instante (`sending`), el foco cae a `<body>` y NO vuelve; a los ~900 ms el estado pasa a `sent`, aparece «Gracias. Te respondemos por correo.» sin región viva y el botón queda deshabilitado para siempre. El usuario de teclado que envió queda tirado al principio del documento sin que nada le confirme el envío. Matiz que la reclamación exagera y hay que declarar: «nunca llega al control» sólo es cierto MIENTRAS está bloqueado; en `ready` el Tab desde el textarea aterriza en el submit (medido), que es el comportamiento correcto del `disabled` nativo. Pero la consecuencia que lo convierte en defecto sí se sostiene: el propio README del block y `state.ts:6-9` presumen de que «un estado bloqueado mudo es imposible», y para teclado/AT lo es a medias — la frase existe en píxeles, no en el árbol de accesibilidad ni en el recorrido. Parte de la cadena es del canon: `FormSubmitProvider` deshabilita nativamente durante el pending (`src/uix/soma/components/form/form-provider.svelte.ts:198`), así que la pérdida de foco al enviar la hereda cualquier `Form.Submit`, no sólo este block.
**Evidencia** — Playwright headless (127.0.0.1:5201), script `…/scratchpad/probe-A-61b.mjs`. Puerta de hidratación: espera a `#uix-blocks-display` + a que no queden `[data-animation-pending]`, y luego ida y vuelta reactiva REAL con `keyboard.type()` (los 3 campos) → el botón pasa de `disabled=true` a `disabled=false`; se vacía el nombre con Ctrl+A/Backspace → vuelve a `disabled=true`. Números: (a) BLOQUEADO — `button[type=submit]`: `disabled` attr = true, `aria-disabled` = null, `aria-describedby` = null. Tab-walk de 6 saltos desde el `<textarea>`: `A(hola@vicen.dev) → BODY → INPUT text → INPUT email → TEXTAREA → A`; el submit NO aparece ninguna vez (`isSubmit:false` en los 6). Inventario de enfocables: 5 elementos, el submit es el único con `disabled:true`. `.focus()` programático sobre él: `activeIsButton:false`. (b) READY — Tab desde el textarea: primer salto = `BUTTON type=submit id=uix-form-submit-s14 disabled:false` (sí se alcanza). (c) LA FRASE — `<span>` «Completa todos los campos para enviar.» / «Resuelve la verificación de arriba.» / «Gracias. Te respondemos por correo.»: `visible:true`, `opacity:1`, `afterButton:true`, `liveRegion:null`, `id:null` (nada a lo que apuntar con `aria-describedby`). (d) ENVÍO — foco en el submit, Enter; muestreo a 30/300/700/1200/1800 ms: `activeElement = BODY` en LAS CINCO tomas; etiqueta «Enviando…» (aria-busy=true, disabled=true) → «Enviado» (disabled=true) y la frase de éxito aparece sin región viva. Tab-walk tras `sent`: el submit sigue fuera del recorrido (6/6 saltos sin tocarlo). (e) `?challenge=on`, campos completos: `disabled=true`, razón «Resuelve la verificación de arriba.», tab-walk pasa por la aguja del ProofOfHuman (`#uix-rotate-align-needle-s18`) y salta al enlace — el submit tampoco aparece. No es ninguno de los falsos positivos catalogados (F15–F22 ni los de medición): la medida es post-hidratación y con reactividad probada.
**Disposición** — Se arregla en el BLOCK, con una pieza en el canon. Block (`src/uix/blocks/contact/`): dar `id` al `Text` de `contact-reason.svelte` y pasar `aria-describedby` a `Form.Submit` en `contact-submit.svelte:21` para que el motivo viaje CON el control; envolver la razón en `role="status"` (o `aria-live="polite"`) para que el cambio de estado —y sobre todo el «Enviado / Gracias…»— se anuncie; y evaluar cambiar `disabled` nativo por `aria-disabled` + no-op en el submit en los estados bloqueados por el usuario (`incomplete`/`unverified`/`rejected`), que es el patrón que mantiene el control en el recorrido y hace audible su explicación. Canon (`src/uix/soma/components/form/form-provider.svelte.ts:198`): la deshabilitación nativa durante `isPending` tira el foco al `<body>` en CUALQUIER `Form.Submit`; ahí toca decidir la retención de foco (o `aria-disabled` + `aria-busy`) a nivel de canon, no del block.
### A-62 — CONFIRMADO · `team` · percepcion · MEDIA
**Mecanismo** — El hecho se sostiene, con una corrección de matiz y otra de atribución.
Cadena: `web/routes/blocks/team/TeamSite.svelte:77` recorta 6 personas con `columns=4` (por defecto) y `:115-122` pone en cada tarjeta dos `IconButton` («Perfil de X», «Escribir a X») SIN `onclick`, SIN `href` y sin ninguna otra vía de resolución → 12 disparadores. `IconButton` compone `Button` (`src/uix/eidos/components/icon-button/icon-button.svelte:23`), cuyo provider dispara el evento de forma incondicional: `src/uix/soma/components/button/button-provider.svelte.ts:129-135` (`onclick` → `runtime.trigger('contact-activate')`), sin mirar si el consumidor pasó handler. El morfo declara ese único evento (`src/uix/morfo/components/button.ts:54-72`, `sequence: 'pre'`, sin intent) y eidos lo materializa: `src/uix/eidos/generated/base.css:6531` → `[data-event-family='contact'][data-event-phase='active'] { animation: press-squeeze … }`. Añádase el canal de sonido, que en el sitio de blocks está ENCENDIDO: cada pulsación crea 2 osciladores + 3 nodos de ganancia.
Por qué es defecto y no mero dato: `docs/CANON.md:244` (regla de composición 1) exige que «contact precede al resultado cuando hay acción directa (agencia)», y el propio morfo cita cap. 22 §8 en `src/uix/morfo/components/button.ts:26-27` — «Contact inicia. Commit resuelve». Aquí el contacto acusa recibo (comprensión visual + tick audible) doce veces y NADA resuelve: sin navegación, sin diálogo, sin toast, sin cambio de estado, sin región viva. Además los dos verbos son navegaciones («Perfil de…» = una página; «Escribir a…» = un mailto) expresadas como `<button>`: la mini-página de team es la ÚNICA del corpus con 0 anclas — banner, cta, faq, hero, site-header, contact, newsletter y pricing resuelven sus navegaciones con `<Link href="#…">`. La excusa «el framework no envía personas de stock» (README del block) no cubre esto: los hermanos usan anclas de humo, no botones muertos.
Matiz que corrijo de la reclamación: «no hacen absolutamente nada» es literalmente falso — sí hay acuse de recibo perceptivo (press-squeeze + tick). Lo que no hay es resolución, que es exactamente lo que la propia reclamación glosa («el contacto inicia y nada resuelve»), así que el defecto se mantiene.
Atribución: el block NO pone los botones. `src/uix/blocks/team/team-member-links.svelte:27` es un `Group` pelado que sólo alinea a los hijos; los 12 `IconButton` son de la demo. Se arregla en la demo, no en `src/uix/blocks/team`.
**Evidencia** — Playwright headless contra http://127.0.0.1:5201/blocks/team/preview (script en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-62.mjs).
Puerta de hidratación: esperé `<style id="uix-blocks-display">` (verifiqué por fetch que NO viene en el SSR: `uix-blocks-display in SSR: false`) y luego a que `[data-animation-pending]` bajara de 9 (presentes en el HTML del SSR) a 0 — ida y vuelta de cliente real, no un atributo estático.
Inventario tras hidratar: 12 `button[data-button]`, 0 `a[href]` en toda la página. Etiquetas: «Perfil de/Escribir a» × Ada Lovelace, Grace Hopper, Alan Turing, Katherine Johnson, Edsger Dijkstra, Barbara Liskov. `type=button`, `data-archetype=trigger`, `cursor: pointer`. (Dato colateral: el SSR sirve esos 12 botones SIN `aria-label` — el nombre accesible sólo aparece al hidratar.)
Emisión (sonda b, MutationObserver atado A CADA NODO): 12 clics de ratón real + 1 Enter con teclado real → 26 registros de `data-event` (13 estampados + 13 des-estampados), idénticos en los 12: `data-event=contact-activate`, `data-event-family=contact`, `data-event-phase=active`, `data-event-id=sig-N`, y `data-event-intent` AUSENTE (correcto: el contacto no lleva intent). `getComputedStyle(...).animationName = press-squeeze` durante la ventana.
Emisión (sonda a, WebAudio parcheado antes de interactuar): baseline tras hidratar = 0 osciladores / 0 buffers; tras cada clic +2 osciladores y +3 nodos de ganancia (2/4/6 en tres clics consecutivos); 26 osciladores en la pasada de 13 activaciones. El canal de sonido está activo en el sitio de blocks.
Resolución = cero, medida a la vez: `docAddsCount = 0` (MutationObserver de documento entero, childList+subtree, excluyendo el interior de los botones), `urlChanged = false`, sin navegaciones de marco, `bodyΔ = 0` en los 12 clics, única región viva `aria-live=assertive` vacía, `consoleErrors = []`. El foco queda en el botón pulsado y ahí muere.
**Disposición** — En la DEMO, no en el block ni en el canon: `web/routes/blocks/team/TeamSite.svelte:115-122`. Sustituir los dos `IconButton` sin destino por el patrón que ya usa todo el resto del corpus de demos — `Link` con `href="#…"` para el perfil y `href="mailto:…"` para el correo (el docstring de `src/uix/blocks/team/team-member-links.svelte:1-6` contempla explícitamente «IconButtons o Links»), de modo que la fila emita `shift-navigate` y la agencia resuelva. Si se prefiere conservar botones, deben llevar un `onclick` que resuelva con su propio evento (`commit.*` / `emerge.open` de un popover de ficha). `src/uix/blocks/team` no se toca: `team-member-links.svelte:27` es un `Group` pelado. Nota de alcance: `web/routes/blocks/site-footer/SiteFooterSite.svelte:144-152` repite el mismo patrón con 3 `IconButton` sin destino — mismo arreglo, hallazgo distinto.
### A-63 — REFUTADO · `contact` · percepcion · MEDIA
**Mecanismo** — Los DOS hechos de la reclamación son ciertos (los medí), pero la cláusula que los convierte en defecto —«es mudo», con la implicación de que el gesto se queda sin canal— no se sostiene, y además el block no tiene palanca alguna.
1. El hecho, confirmado: el `<a data-link data-variant=subtle>` de `web/routes/blocks/contact/ContactSite.svelte:71` es el ÚNICO interactivo de la columna de datos (censo en vivo de la página: 2 `input`, 1 `textarea`, 1 needle de ProofOfHuman, 1 `button[data-form-submit]` deshabilitado y ESTE enlace; los otros tres `Contact.Detail` son prosa). Clic y Enter: 0 osciladores, 0 `createBufferSource`, 0 `data-event*` en todo el documento, 0 mutaciones en el nodo.
2. El silencio NO es deriva: es contrato escrito, y es de canon, no del block. `src/uix/morfo/components/link.ts:11-16` declara literalmente «Passive on the morfo surface: Link has hover/active visual states but no semantic events. Click / navigation is the consumer's responsibility», y `:21` le da `scope: ['eidos']` — sin capa soma no existe `runtime.trigger` que disparar, no hay contradicción doc-vs-código. `src/uix/eidos/components/link/README.md:80-86` («Link declara 0 eventos en su morfo») y `:95` difieren el evento `commit-navigate` explícitamente bajo la regla 2-de-3. Contrasta con el caso hermano de `navigation-menu`, que sí es defecto porque su pack promete `commit-select` y el provider nunca lo emite.
3. No hay palanca en el block, ni debe haberla: `docs/architecture/blocks.md` B-1 («un block no declara morfo, no envía pack de sema») y B-2 («todo interactivo es un componente canónico de eidos — `Button`, `Link`, `Field`…», nombrando `Link`). El block compone exactamente lo que el contrato le manda.
4. La cláusula perceptiva es falsa medida: reposo opacity 1 / `text-decoration: none` → hover opacity 0.8 + `underline` (`src/uix/eidos/components/link/link.css:46-51` y `:65-69`), y con Tab real `:focus-visible` pinta anillo `2px solid` (`link.css:75-78`); `cursor: pointer` en los tres estados. Según `docs/CANON.md` §7 la robustez es «una lectura que sobrevive a la pérdida de un canal»: el canal visual está presente.
RESIDUOS MEDIDOS que la reclamación NO formula y que no la rescatan: (a) en el instante del press el delta es CERO — `[data-link][data-variant='subtle']:hover` (0,2,1) de `link.css:65-69` gana por especificidad a `[data-link]:active` (0,1,1) de `link.css:71-73`; hecho de CSS de canon, idéntico al residuo ya registrado en A-50. (b) el `href` es `#correo`, un fragmento MUERTO (no existe elemento con ese id; `scrollY` 0→0 tras el clic) sobre lo que es una dirección de correo — debería ser `mailto:hola@vicen.dev`; eso es un defecto de autoría de la demo, de otra naturaleza que «no emite nada», y merecería id propio. Confianza alta: todo medido tres veces con las dos sondas.
**Evidencia** — URL medida: http://127.0.0.1:5201/blocks/contact/preview (chromium headless 1280x900, Playwright por ruta absoluta). Scripts: C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-63.mjs, probe-A-63b.mjs, probe-A-63c.mjs.
PUERTA DE HIDRATACIÓN: esperé `document.getElementById('uix-blocks-display')` (lo escribe el `$effect` de `web/routes/blocks/_lib/BootUix.svelte:91-97`), y después ida y vuelta reactiva REAL con teclado: `page.keyboard.type('Ana')` → `input.value === 'Ana'` y `:placeholder-shown` = false. Luego esperé a 0 nodos `[data-animation-pending]`.
SONDA (a): parcheé `createOscillator`/`createBufferSource`/`createGain` en `AudioContext.prototype` vía `addInitScript`, ANTES de cualquier script de la página, más un Proxy sobre el constructor. SONDA (b): MutationObserver atado AL NODO concreto (attributes + attributeOldValue + subtree) y un segundo barrido documento-completo de `data-event*`.
CONTROL POSITIVO (las sondas SÍ detectan emisión): en /blocks/faq/preview, clic en un trigger de accordion → **osc=2**, ctx.state='running', y 8 mutaciones: `data-event=expand`, `data-event-id=sig-0`, `data-event-phase=active`, `data-event-family=emerge` y su retirada al cerrar el hold. La galería enciende los canales a propósito (`BootUix.svelte:62`, `events:{sound:true,haptic:true}` + los 72 packs de `_lib/sema-packs.ts`).
EL ENLACE (mismo run, mismas sondas):
- Clic de ratón: osc=0, buf=0, 0 mutaciones en el nodo, 0 `data-event*` en el documento. URL pasó a `…/preview#correo`, `scrollY` 0→0.
- Enter con el enlace enfocado: osc=0, buf=0, 0 mutaciones. Mudez de sema total, ratón y teclado.
- Atributos del nodo: `href=#correo`, `data-link`, `data-variant=subtle`, `data-underline=hover`, `data-color=primary`. Ningún `data-event*` presente jamás.
VISUALES (getComputedStyle sobre el nodo real, ratón retirado a (5,5) para el reposo):
- reposo: color oklch(0.61 0 0), opacity **1**, text-decoration none, outline 0px, cursor pointer.
- hover (`:hover`=true): opacity **0.8**, text-decoration **underline** → hay delta.
- press (`:active`=true, botón mantenido): color/opacity/decoration/transform IDÉNTICOS al hover → delta cero en el instante del clic (residuo (a)).
- Tab real (`:focus-visible`=true): outline `2px solid color(srgb 0.745076 0.576585 0.894185 / 0.48)` + underline.
CENSO de interactivos de la página (`a,button,input,textarea,select,[role=button],[tabindex]`): 6 — INPUT(fieldInput) x2, TEXTAREA(textareaInput), DIV(rotateAlignNeedle), BUTTON «Enviar mensaje» disabled=true, A «hola@vicen.dev». El 1-de-1 de la columna de datos que afirma la reclamación es EXACTO.
FALSOS POSITIVOS descartados: no es F15–F22; no medí antes de hidratar (crucé la puerta + ida y vuelta reactiva); pestaña activa y 0 `[data-animation-pending]`; usé teclado real (`keyboard.type`/`press`), no `fill()`.
### A-64 — ARREGLADO · `pricing` · percepcion · MEDIA
**Mecanismo** — Ver A-23: sin clic muerto no hay `commit-toggle` que anuncie algo que no pasó.
**Evidencia** — Ver A-23.
### A-65 — CONFIRMADO · `pricing` · percepcion · MEDIA
**Mecanismo** — La cadena es real y la mitigación que el propio fichero declara no funciona. (1) `<Button>` declara su evento de morfo `contact-activate`, familia `contact`, `sequence:'pre'` — `src/uix/morfo/components/button.ts:52-70`. Al soltar el ratón, sema proyecta `data-event-family="contact"` + `data-event-phase="active"` sobre el propio `<button>` (`src/uix/sema/projection/dom.ts` vía `src/uix/sema/stamp.ts:50-58`). (2) El handler de la demo `choose()` en `web/routes/blocks/pricing/PricingSite.svelte:29-46` hace `await tick()` y emite un SEGUNDO signal, `commit-select` / familia `commit` / intent `affirm`, con `target = event.currentTarget` — es decir, EL MISMO nodo. (3) `DomSignalProjector.project()` no serializa por target: reescribe `data-event-*` incondicionalmente (su propio test lo fija: `src/uix/sema/projection/dom.test.ts:70-72`, «Overwrite some attrs with a second projection»). (4) `await tick()` es un flush de microtarea: medido, los dos stamps distan 1,1 ms — muy por debajo de un frame (16,7 ms). El navegador nunca llega a recalcular estilo con `data-event-family="contact"`, así que la regla `[data-event-family='contact'][data-event-phase='active'] { animation: press-squeeze var(--duration-fast) … }` (`src/uix/eidos/generated/base.css:6531-6533`) jamás se activa: no hay `animationstart` de `press-squeeze` ni siquiera un `animationcancel`. Sólo corre `announce-pulse-affirm` (la firma de `commit`+`affirm`, 320 ms). El comentario de `PricingSite.svelte:33-37` afirma literalmente que el `tick()` «es lo que evita que las dos señales colisionen en el mismo elemento»; la medición demuestra que no lo evita. Calibración honesta (por eso MEDIA y no ALTA): el press no queda mudo del todo — la capa de estado `[data-button]:active { transform: scale(var(--press-scale)) }` (`src/uix/eidos/components/button/button.css:107-114`, 0.985 / 80 ms) sí corre, y el canal de sonido tampoco se pisa (ambas señales suenan). Lo que se destruye es la firma transmodal de `contact` (press-squeeze: escala 0.96 + achatado de depth + endurecido de shape, `docs/theming/channels.md:96`), o sea el momento «contact inicia» que el propio morfo del Button documenta como la mitad del arco (`button.ts:31-36`).
**Evidencia** — Playwright headless contra http://127.0.0.1:5201, script en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-65.mjs (+ …-control.mjs). Puerta de hidratación: esperé `style#uix-blocks-display`, luego 0 `[data-animation-pending]`, y un ida y vuelta reactivo real (clic en «Anual» → precios 0/29/99 € → 0/23/79 €, confirmado hidratado). Sondas: (a) contador WebAudio parcheando `createOscillator`/`createBufferSource` en un addInitScript ANTES de interactuar; (b) MutationObserver ligado AL NODO del botón (no a un selector) sobre `data-event*`, más `animationstart/end/cancel` y un muestreo de `getAnimations()` por frame. — CASO /blocks/pricing/preview, CTA «Probar 14 días» (t=0 en mouseup): 95,5 ms `data-event-family: null→"contact"`; 96,6 ms `data-event-family: "contact"→"commit"` (+`data-event-intent: null→"affirm"`, `data-event-id: sig-1→sig-2`) ⇒ Δ = 1,1 ms, cero frames entre medias (el rAF siguiente cae en 100,2 ms). `animationstart` observado: SÓLO `announce-pulse-affirm` (110,6 ms → `animationend` 427,3 ms, elapsed 0,32 s). NINGÚN `press-squeeze`, ningún `animationcancel`. Idéntico en el CTA «Empezar gratis»: Δ = 0,7 ms (95,7 → 96,4 ms), sólo `announce-pulse-affirm`. Nodos de audio: +4 osciladores en ese único clic ⇒ el canal de sonido sí recibe las dos señales; la colisión es exclusivamente de la proyección visual. — CONTROL A/B (misma CSS, misma versión, Button cuyo clic emite SÓLO el `contact-activate` del morfo): /blocks/cta/preview → 92,5 ms stamp `contact`, `animationstart press-squeeze` 109,5 ms, `animationend` 226,6 ms elapsed 0,12 s, desestampado 228,8 ms. /blocks/hero/preview → stamp 71 ms, `animationstart press-squeeze` 93,3 ms, `animationend` 206,5 ms elapsed 0,12 s. En ambos verifiqué que la regla servida existe: `[data-event-family="contact"][data-event-phase="active"] { animation: press-squeeze var(--duration-fast) var(--ease-default); }`, con `--duration-fast: 120ms` y `--duration-slow: 320ms` leídos del nodo. Conclusión del A/B: press-squeeze corre entero donde no hay segundo emit y no arranca nunca donde lo hay — la causalidad queda aislada.
**Disposición** — Se arregla en la DEMO (web/routes/blocks/pricing/PricingSite.svelte:29-46), no en el block: `src/uix/blocks/pricing/pricing-plan-action.svelte` es un `<Box>` y no emite nada. Dos vías: (a) mínima — sustituir `await tick()` por una espera que sobreviva al hold del contact (`glimpse` = 120 ms, `src/uix/sema/holds.ts` + `durations.ts`), p.ej. programar el `commit-select` con `uix.timers` tras el hold, y de paso corregir el comentario de las líneas 33-37, que hoy afirma una garantía que no existe; (b) mejor — emitir el commit sobre otro target (la tarjeta del plan / `Pricing.Plan`), que es además más fiel al canon: el contacto es del botón, la consecuencia es del plan elegido. Complemento a nivel de CANON (no obligatorio para cerrar este hallazgo, pero es la causa raíz genérica): `DomSignalProjector.project()` (src/uix/sema/projection/dom.ts) no serializa por target — dos signals sobre el mismo nodo se pisan sin que nadie lo note. Como el propio morfo del Button enseña ese patrón («Consumers wanting deferred semantic feedback … emit their own commit.\*», button.ts:31-36), convendría que el engine encolara o al menos avisara en dev cuando una proyección se sobrescribe antes de haber pintado un frame.
### A-66 — REFUTADO · `pricing` · percepcion · MEDIA
**Mecanismo** — El HECHO es cierto y lo he medido, pero la CONSECUENCIA que lo convertiría en defecto no se sostiene: la reclamación colapsa dos canales que el canon mantiene separados a propósito.
1. Lo que hace el código. `web/routes/blocks/pricing/PricingSite.svelte:141` grada el CTA con `intent={plan.featured ? 'fulfill' : 'affirm'}`, y `PricingSite.svelte:38-45` emite siempre `commit-select` con `intent: 'affirm'`, sin mirar `featured`. Medido: el destacado pinta `data-color="fulfill"` y los tres emiten `data-event-family=commit / data-event-intent=affirm` idéntico.
2. `intent` en Button NO es el canal perceptual. `src/uix/morfo/components/button.ts:18-34` lo declara literalmente prop VISUAL ("Button.intent es un VISUAL prop only… no carga el evento sema"), y `button.ts:61-71` deja `contact-activate` SIN intent citando cap. 22 §11; cap. 22 §8 nombra `contact.press + fulfill` ANTIPATRÓN ("el sistema dice 'ya terminó' cuando solo ha empezado"). El prop desemboca en `data-color` (`button.ts:99-121` + `src/uix/soma/components/button/button-provider.svelte.ts:81-82`), que es un consumidor INDEPENDIENTE del mismo prop, no el complemento del canal perceptual (doctrina explícita del proyecto: `data-color` nunca explica el intent percibido). No existe, pues, ninguna "lectura unificada" contratada entre el tinte del disparador y el intent de la consecuencia: el canon prohíbe justo ese eco.
3. El intent es intrínseco AL ACTO, no heredado del contexto del componente. El acto que describe `commit-select` es "quedó fijada una elección", y es el MISMO en los tres planes; que el vendedor recomiende uno es contexto aguas abajo (misma forma que el `close-save` del Dialog, que no hereda el peso destructivo del diálogo). `docs/CANON.md` §«Intent assignment» canoniza elegir/deselegir un ítem = `affirm`; `fulfill` es "objetivo cumplido (resuelve tensión)" (§3), que no describe escoger una tarifa. Y `docs/CANON.md` §5 llama antipatrón capital cargar el intent desde el marco: graduar `fulfill` sólo al plan destacado haría que el sistema dijera "objetivo cumplido" por preferencia comercial del vendedor, que es el defecto que la reclamación pide introducir.
Conclusión: los tres commits suenan igual porque describen el mismo acto; el verde del destacado es jerarquía visual, no una afirmación evaluativa sobre la consecuencia. Confianza alta (doctrina explícita en tres sitios + medición).
Observación adjunta que NO es este hallazgo (no lo confirmo bajo A-66): dentro del propio plan destacado el acento visual sí se parte — `src/uix/blocks/pricing/pricing-plan.svelte:31` y el badge de `PricingSite.svelte:116` usan `color="primary"` (violeta, oklch h≈306) mientras el CTA salta a `fulfill` (verde, oklch h≈158). Eso es coherencia de acento en app-land, no la ruptura sonora reclamada.
**Evidencia** — Sonda Playwright headless (script propio en scratchpad/probe-A-66.mjs y -A-66b.mjs) sobre http://127.0.0.1:5201/blocks/pricing/preview.
Puerta de hidratación: esperado `document.getElementById('uix-blocks-display')`, luego 0 nodos `[data-animation-pending]`, y IDA Y VUELTA REACTIVA REAL — clic en el toggle "Anual" cambia los precios de "29 €/99 €" a "23 €/79 €" y vuelve con "Mensual" (hydrated=true).
Estado visual (compuesto pintado, no el token): CTA #0 "Empezar gratis" data-color=affirm, variant=surface, bg oklch(0.9937 0.0044 179.74), fg oklch(0.5521 0.101 178.79); CTA #1 "Probar 14 días" data-color=fulfill, variant=solid, bg oklch(0.6406 0.1329 157.68), fg blanco; CTA #2 "Hablar con ventas" idéntico al #0. Tokens: --color-primary-solid oklch(0.5556 0.1829 305.86), --color-affirm-solid oklch(0.6491 0.1136 181.96), --color-fulfill-solid oklch(0.6406 0.1329 157.68). Tarjetas: neutral / primary(borde oklch h=309.69) / neutral; badge primary oklch(0.5556 0.1829 305.86).
Emisión sema con LAS DOS sondas. (a) MutationObserver ligado a CADA nodo botón (no a selector), clic real de ratón: los TRES producen exactamente la misma secuencia — `data-event=contact-activate` + `data-event-family=contact` SIN `data-event-intent` (t+0 ms), luego `data-event=commit-select` + `data-event-family=commit` + `data-event-intent=affirm` (t+2 ms), y limpieza a null a los ~337 ms. Cero diferencia entre el destacado (b1) y los otros dos. (b) Contador WebAudio parcheado en `AudioContext.prototype.createOscillator/createBufferSource` ANTES de interactuar: 4 osciladores y 0 buffer-sources por clic, el mismo número en los tres — el sonido del destacado es el mismo que el de los otros, tal como afirma el hecho.
### A-67 — CONFIRMADO · `faq` · percepcion · MEDIA
**Mecanismo** — El mecanismo se sostiene entero y es del CANON, no del block. `Faq.List` fija `type='single'` (src/uix/blocks/faq/faq-list.svelte:14) — que además ya es el default del Accordion canónico (src/uix/soma/components/accordion/components/accordion.svelte:14). Con selección única, un clic sobre OTRA pregunta pasa por `AccordionProvider.toggleItem` → `setValue` (src/uix/soma/components/accordion/accordion-provider.svelte.ts:113-120), que tras un solo `await tick()` recorre dos bucles seguidos en el MISMO tick: `for (const value of closing) …emitToggleEvent('collapse')` (línea 119) y `for (const value of opening) …emitToggleEvent('expand')` (línea 120). Son dos `runtime.trigger` distintos sobre dos ítems distintos → dos señales sema `emerge` (morfo: src/uix/morfo/components/accordion.ts:28 y :46, ambas `sequence:'post'`). El pack de sonido las tuñe por separado: `expand` = `emerge.soft` (gain 0.08) y `collapse` = `emerge.exit` con `gain:0.06` (src/uix/sema/components/accordion.ts:32-38), sobre la base de familia `emerge` (pitch 600, contour ascendente, gain 0.2 — src/uix/sema/sema-map.ts:156-170); `emerge.exit` resta 120 Hz y pone contorno descendente (src/uix/sema/sounds.ts:155). El árbitro que debería arbitrar la superposición NO actúa: `applyDominance` (src/uix/sema/engine.ts:559-593) solo silencia si una ocurrencia activa tiene rango ESTRICTAMENTE mayor (`active.rank > rank`, línea 576), y aquí ambas son estructurales sin intent → `occurrenceRank` = 0 para las dos (líneas 224-227), empate ⇒ ninguna se atenúa; y la primera (collapse) ya tiene su nota agendada, así que tampoco puede callarse a posteriori. `EngineSound.synthesize` (src/arts/sound/engine-sound.ts:517-589) agenda ambas en el mismo `ctx.currentTime`, sin límite de polifonía ni ducking entre earcons. Resultado: un gesto, dos notas de 150 ms exactamente coincidentes con glissandi opuestos. Matiz donde la reclamación se pasa de rosca: «sin atenuar» es impreciso en absoluto — ambas SÍ están atenuadas respecto a la base de familia (0.08 = 40 % y 0.06 = 30 % de 0.2) y la suma lineal (0.14) queda por debajo de un solo `emerge.strong` (0.15), o sea no hay pico de volumen; lo que no existe es atenuación de una FRENTE A la otra, que es la sustancia de la reclamación (0.06 vs 0.08 = −2,5 dB, «peso casi igual»). El defecto es de legibilidad perceptual y contradice la propia regla de simultaneidad del canon (docs/CANON.md:253-254: «on a tie, the most recent»), que en el empate del mismo tick no se aplica en ninguna dirección.
**Evidencia** — Playwright headless sobre http://127.0.0.1:5201/blocks/faq/preview (scripts probe-A-67.mjs y probe-A-67b.mjs en el scratchpad). Puerta de hidratación: espera a `document.getElementById('uix-blocks-display')`, luego a 0 nodos `[data-animation-pending]`, y un ida y vuelta reactivo real (clic en el trigger 0 → `aria-expanded` pasa a `true`). Sonda (a) contadores WebAudio parcheando `AudioContext.prototype.createOscillator/createBufferSource` y `OscillatorNode.prototype.start` ANTES de interactuar; sonda (b) MutationObserver atado a los NODOS `[data-accordion-item]` concretos. Gesto medido = un solo clic en el trigger 1 con el ítem 0 abierto. Números: 4 osciladores arrancados, 0 bufferSources — voz A fundamental 480 Hz + quinta 720 Hz, voz B 600 Hz + 900 Hz; los CUATRO con el mismo `when` de AudioContext (1.4628571428571429 en la corrida b; 1.401904761904762 en la a) y el mismo fin (1.6128571428571428) ⇒ solape del 100 % durante 150 ms. Envolventes: pico 0.06 (voz 480, la de collapse) y 0.08 (voz 600, expand), ambas con rampa de caída al mismo instante. Contornos opuestos confirmados en el detune: owner de 480 Hz `setValueAtTime(+400)` → `linearRampToValueAtTime(-400)` (DESCENDENTE); owner de 600 Hz `setValueAtTime(-400)` → `linearRampToValueAtTime(+400)` (ASCENDENTE). Estampas visuales del MutationObserver en el mismo milisegundo: ítem 0 `data-event=collapse` / `data-event-family=emerge` / `sig-1`, ítem 1 `data-event=expand` / `emerge` / `sig-2`. Control (segundo clic sobre el mismo ítem abierto, un solo cambio de estado): solo 2 osciladores (480 + 720), una rampa 0.06, una estampa `collapse` ⇒ el doblete es específico del cambio de pregunta. Sonido activo por configuración real de la página: `events: { sound: true, haptic: true, components: semaPacks }` en web/routes/blocks/\_lib/BootUix.svelte:62 (y en web/routes/blocks/+layout@.svelte:61), con `dominance` en su default `true` (src/uix/sema/engine.ts:257).
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
**DISPOSICIÓN CORREGIDA (2026-08-17) — APLAZADA al eje de sema, con las tres puertas de abajo descartadas y una cuarta que ninguna nombraba.**
Contrastadas contra `engine.ts`, `CANON.md` §8 y `sema.md`, las tres opciones originales fallan:
1. **Tuñar el par en el pack** — inimplementable como está escrito. Una regla de cascada matchea los `data-event-*` del ítem estampado y **no puede distinguir** el `collapse` de un intercambio del `collapse` de un cierre a secas: son el mismo evento sobre el mismo tipo de nodo. Distinguirlos pediría un `:has()` sobre el hermano, manuscrito y dependiente del orden de emisión — dos violaciones (selector a mano + acoplamiento al orden).
2. **Resolver el empate en `applyDominance`** — malinterpreta al árbitro. El `>` estricto YA implementa la mitad del «on a tie, the most recent» que un filtro de LLEGADA puede implementar: el recién llegado no se mutea, o sea el más reciente gana. La otra mitad — atenuar al anterior — exige revocar una nota ya agendada, y `EngineSound` no tiene ducking ni handle de revocación. Tocaría la librería de sonido y cambiaría la conducta de TODO empate del ecosistema por el caso de un componente.
3. **Un solo evento para el intercambio** — rompe el modelo de commits: cada evento lleva su `commits` (`data-state` open/closed) sobre SU ítem, y un evento único no puede estampar dos estados en dos nodos.
**La puerta correcta: un evento con NUANCE declarado — `emerge-collapse-swap`.** La forma es canónica (`{family}-{verb}[-{nuance}]`, ejemplo vivo `emerge-dismiss-outside`) y el provider lo sabe de forma determinista en `setValue`: `closing` y `opening` no vacíos a la vez sólo ocurre en el intercambio de `type='single'`. Con eso el pack lo NOMBRA (`SILENT`, o algo sub-audible) → un gesto, un sonido, que es justo lo que la dominancia del libro pediría; los dos estampados VISUALES se quedan (dos superficies distintas, sin conflicto — el aire es lo único que es uno); eidos no se entera, porque sus selectores por prefijo (`[data-event^='emerge-collapse']`) siguen matcheando el nuance; y el engine y `$sound` no se tocan.
Coste: morfo + provider + pack. Cae en el eje de sema, así que **no se ejecuta aquí** — se ejecuta en una sesión suya, con esta puerta ya elegida.
**Disposición original (superada)** — Canon, no block: en src/uix/blocks/faq no hay nada que tocar (solo elige `type='single'`, que es el default del Accordion canónico). Tres sitios posibles, por orden de preferencia: (1) pack sema del accordion — src/uix/sema/components/accordion.ts, dar al par swap una tuña propia (p. ej. que el `collapse` coincidente baje a `emerge.exit.soft`/gain ~0.02 o se calle, dejando el `expand` como único signo del gesto); (2) árbitro de dominancia — src/uix/sema/engine.ts:559-593, resolver el empate del mismo tick según docs/CANON.md:253-254 («on a tie, the most recent») atenuando la ocurrencia estructural anterior en vez de exigir `>` estricto; (3) soma — src/uix/soma/components/accordion/accordion-provider.svelte.ts:119-120, emitir un único evento para el intercambio en modo `single` en lugar de collapse+expand (esta opción cambia el contrato del morfo, así que es la más cara). Severidad: la declarada MEDIA es razonable como problema de legibilidad, no de volumen (la suma 0.06+0.08 = 0.14 queda por debajo de un solo `emerge.strong` = 0.15).
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-68 — CONFIRMADO · `stats-band` · percepcion · MEDIA
**Mecanismo** — La banda monta DOS disparadores de viewport independientes que nadie sincroniza. (1) La entrada: `stats-band.svelte:41` pone `data-stagger` en el AutoGrid y `stats-band-stat.svelte:18` hace de cada stat un `<Motion trigger="viewport">`; la fundación escribe `--motion-stagger-index` por `:nth-child` (`src/uix/eidos/lib/render-css.ts:930`) y el preset retrasa la entrada `index × --motion-stagger-each`, con default 70 ms para animadores de viewport (`src/uix/eidos/components/motion/motion.css:41-43`) y relleno `backwards` que mantiene el fotograma inicial —opacidad 0— hasta que llega su turno (`render-css.ts:1117-1118`). (2) El contador: `stats-band-value.svelte:22` compone `<CountUp to={count} {...countOptions} />` sin pasar `startWhen` ni `delay`, y `CountUp` trae SU PROPIO IntersectionObserver sobre el span interior con opciones por defecto —threshold 0, sin rootMargin— (`src/uix/eidos/components/count-up/count-up.svelte:149-160`), mientras que el de `Motion` exige threshold 0.1 y `rootMargin: '0px 0px -10% 0px'` (`motion.svelte:44-61`). Un IntersectionObserver ignora la opacidad: el span cuenta aunque su ancestro esté a `opacity: 0`. Resultado: los contadores arrancan todos a la vez y ANTES que cualquier revelado, y cada cifra se hace visible con su muelle ya avanzado. La atribución no es sólo del block: `Motion` no expone su momento `seen` (la variable es privada, `motion.svelte:42`) y el índice de stagger sólo existe en CSS, así que el block no tiene hoy con qué alimentar el `startWhen`/`delay` que `CountUp` sí ofrece (`count-up.svelte:31,34`). El defecto surge en la costura que monta el block, pero la pieza que falta está en canon.
**Evidencia** — Playwright headless (script en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-68.mjs) sobre http://127.0.0.1:5201/blocks/stats-band/preview, 1280x800, puerta de hidratación por `style#uix-blocks-display` + espera a que no quede `[data-animation-pending]` (rAF vivo: se muestrearon 394 fotogramas y las opacidades sí rampan). TEST A (banda en pantalla al cargar, 4 stats del demo: 12.500 / 340 / «99,98 %» sin count / 48): los tres contadores pintan su valor inicial en el MISMO fotograma (t=3171,3 ms) y avanzan en paralelo; las opacidades salen de 0 escalonadas a t=3252,9 / 3320,4 / 3386,2 / 3453,9 ms (deltas de ~67 ms = el stagger de 70 ms). En el fotograma en que cada cifra deja de ser invisible marca: stat[0] «0» (0 %), stat[1] «18» de 338 (5,3 %), stat[3] «10» de 48 → **20,8 % del valor final**, que es exactamente el 21 % reclamado. TEST B (misma página con la banda empujada bajo el pliegue por CSS y entrada por scroll lento, que es como la lee un humano): con el borde superior de la banda a 6 px dentro del viewport los cuatro stats siguen `data-animation-pending`, `opacity: 0`, y los contadores YA corren (1144 / 31 / 4); tras 2,6 s en esa posición siguen invisibles (pending=true, opacity 0) con los contadores prácticamente terminados (12.121 / 330 / 47); al completar el scroll la primera cifra aparece con «12.160» de 12.428 = **97,8 %**, es decir, el count-up entero ocurre fuera de la vista y el lector ve la cifra ya hecha. No es ninguno de los falsos positivos catalogados (F15–F22 ni pestaña en segundo plano: el pending se limpió y las animaciones corrieron).
**Disposición** — La costura es del block (`src/uix/blocks/stats-band/stats-band-value.svelte:22` compone `CountUp` sin gate; `stats-band-stat.svelte:18` pone el `Motion`), pero el arreglo necesita canon: que `src/uix/eidos/components/motion/motion.svelte` exponga su momento de revelado (un `onEnter` o el `seen` como parámetro del snippet, hoy privado en la línea 42) y, si se quiere sincronía exacta, el retardo de stagger efectivo — que hoy sólo vive en CSS (`motion.css:41-43` + `render-css.ts:1118`). Con eso, `StatsBand.Value` alimenta el `startWhen`/`delay` que `CountUp` ya acepta (`count-up.svelte:31,34`) y la cifra empieza a contar cuando aterriza, no antes. Alternativa mínima en canon sin tocar el block: alinear el observador de `CountUp` con el de `Motion` (mismo threshold/rootMargin) — corrige el caso del scroll lento pero NO el desfase de 210 ms del stagger.
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
**⚠️ EL DILEMA ERA FALSO (2026-08-17), y lo planteé yo.** «Escalonar el arranque y aterrizar juntas son incompatibles» es cierto, pero **exponer el `seen` de `Motion` no implica escalonar nada**: el retardo del stagger vive en `animation-delay` (CSS), mientras que los observadores de los cuatro `Motion` de la banda disparan en el MISMO instante — los stats están en una fila, a la misma altura. Gatear los contadores en el REVELADO da arranque conjunto → duraciones iguales (λ derivada, A-69) → aterrizaje conjunto intacto. Se presentó como una decisión entre dos males y no lo era: la opción buena estaba fuera de la lista.
Corolario sobre la alternativa «mínima» que llegué a recomendar (alinear el observador de `CountUp` con el de `Motion`): es **acoplamiento por copia** — duplica `threshold` + `rootMargin` en otro fichero, la clase exacta de deriva que el framework elimina con builders tipados, y cambia los defaults de `CountUp` para todos sus usos sueltos. Descartada.
**ARREGLADA (2026-08-17)** — `stats-band-value.svelte` lee `getMotionContext()` (la costura de [A-93]) y pasa `startWhen={motion?.seen ?? true}` a `CountUp`, ANTES del spread de `countOptions`, para que el app pueda desactivar el gate explícitamente. Cero cambios en `CountUp`, cero observadores propios en el block, B-6 intacto.
**Medido tras el arreglo** — Playwright headless, 1280×800, sonda `probe-A68-after.mjs`. ⚠️ **La primera corrida fue INVÁLIDA y se descarta**: insertó el espaciador DESPUÉS de cargar, y como `Motion` es `once: true` su observador ya había disparado con la banda en pantalla (`pending: 0`, opacidades a 1). El espaciador tiene que existir en el primer pintado — se instala por `addInitScript` con una hoja de estilo. 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`, sin moverse.
- **La ventana que discrimina** — borde superior de la banda a **6 px dentro** del viewport, donde el IO de `CountUp` (threshold 0) dispara y el de `Motion` (0.1 / −10 %) no: contadores a `0 / 0 / 0` en la llegada **y** tras 2,6 s. Antes, en esa misma ventana, corrían (1144 / 31 / 4) y acababan prácticamente terminados (12.121 / 330 / 47).
- **Al revelarse**: la primera cifra aparece al **0 %** de su valor (antes: **97,8 %**).
- **A-69 intacta**: las tres aterrizan en t = 1781 ms, **dispersión 0 ms**.
**Residuo, honesto y peor que mi estimación** — las cifras que el stagger revela más tarde ya llevan camino andado cuando se hacen visibles: 16,8 % la segunda (+70 ms) y **35,4 %** la tercera (+210 ms). Estimé «≤10 %» y la medición dice 35,4 %: el muelle es sobreamortiguado y cubre mucho recorrido al principio, así que 210 ms pesan bastante más que su fracción de los 2 s. Es el precio del aterrizaje conjunto que A-69 firmó, y no se puede bajar sin reabrirla — retrasar cada arranque por su índice devolvería el escalonado. Queda documentado, no escondido.
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
**Disposición corregida (2026-08-10, contraste doctrinal)** — **No es arreglable desde el block sin romper el contrato B.** `theming/motion.md` §D.11 dice que «the children's timing is per-component realization», así que sincronizar contador y revelado SÍ es trabajo del block; pero `Motion` guarda su `seen` en `$state` privado y lo único observable es el `data-animation-pending` del DOM, y leerlo exigiría que el block monte un observador propio — **exactamente lo que B-6 prohíbe** («needing to observe something is the admission rule firing»). La pieza que falta tiene nombre: `Motion` necesita exponer el momento visto, como `Cascade` ya lo expone por contexto. ⚠️ Y hay un conflicto que hay que FIRMAR antes de tocar nada: A-69 acaba de fijar en el canon que la banda **aterrice junta** («by construction», medido: 2005 ms × 3, desfase 0), y arrancar cada cifra con su retardo de entrada rompería ese aterrizaje. Escalonar el arranque y aterrizar juntas son incompatibles.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
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
### A-69 — ARREGLADO · `stats-band` · percepcion · MEDIA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — El muelle de `count-up` está sobreamortiguado y su parada depende del TAMAÑO de la cifra, no de `duration`. En `src/uix/eidos/components/count-up/count-up.svelte:180-181` los parámetros son `damping = 20 + 40/duration` y `stiffness = 100/duration`; con el valor por defecto `duration = 2` → stiffness 50, damping 40 → w0 = 7.0711, zeta = 2.8284 (>1, sobreamortiguado). La forma cerrada de `runSpring` (líneas 108-133) se reduce, pasado el transitorio, a |y(t)| = 1.0345·|d0|·e^(−1.2917·t), con d0 = from − to. La condición de parada es `restThreshold = 0.5 * 10^-decimals` (línea 169), es decir 0.5 absoluto para enteros, luego el tiempo de asentamiento es t = ln(2.069·|d0|)/1.2917: crece con el LOGARITMO de la magnitud. `duration` sólo desplaza λ, nunca cancela el término log|d0| — medido, la razón settle(12500)/settle(48) = 2.21 constante para duration de 0.3 a 2, así que NINGÚN valor global de `duration` hace aterrizar juntas las cifras. `web/routes/blocks/stats-band/StatsBandSite.svelte:25-30` pone 12500 / 340 / 48 y `stats-band-value.svelte:22` monta `<CountUp to={count} />` sin `countOptions`, o sea con el default. Consecuencias reales, las tres verificadas en navegador: (1) los tres contadores arrancan en el MISMO frame y terminan a 3,57 / 5,09 / 7,88 s — 4,31 s de dispersión; (2) `duration = 2`, documentado en `types.ts:21` como «approximate count duration in seconds», da 3,9× para la cifra de cabecera; (3) `onEnd` se dispara en `delay + duration` (línea 189), 5,88 s ANTES de que la cifra grande deje de moverse. La cola no es imperceptible: el último dígito de 12.500 salta a 6,05 / 6,17 / 6,37 / 6,65 / 7,05 / 7,88 s, seis cambios visibles en los dos segundos finales.
**Evidencia** — Playwright headless (probe-A-69b.mjs) sobre http://127.0.0.1:5201/blocks/stats-band/preview. Puerta de hidratación: esperado `<style id="uix-blocks-display">` (3,95 s); comprobado document.hidden=false, 0 nodos [data-animation-pending], prefers-reduced-motion=false, 3 nodos [data-count-up-value] con top=70 y en viewport. Muestreo DIRECTO de textContent cada 40 ms durante 25 s (la primera sonda con MutationObserver se descartó: entrega asíncrona en microtarea → valores sesgados). Arranque simultáneo: firstT = 0,04 s en los tres. Último cambio visible: 12.500 → 7,88 s · 340 → 5,09 s · 48 → 3,57 s. Modelo analítico independiente: 7,866 / 5,076 / 3,560 s — coincide con lo medido dentro de ±0,02 s. Valores finales correctos (relectura directa a 25 s y a 33 s: 12.500 / 340 / 48). Barrido de `duration` (0,3–2): settle(12500) 4,60→7,87 s y settle(48) 2,08→3,56 s, razón 2,21 invariante. Los números literales de la reclamación (7,9 s y 3,6 s) se reproducen exactamente. Anomalía NO reproducida y por tanto no imputada: en la primera sonda, con el MutationObserver global activo, el nodo grande quedó en 12.499 a los 18 s; la sonda limpia no lo reproduce.
**Disposición** — Se arregla en CANON, no en el block: `src/uix/eidos/components/count-up/count-up.svelte:169` (+180-181, 189). El umbral de reposo debe ser relativo al recorrido (p. ej. `restThreshold = max(0.5*10^-decimals, |to-from| * eps)`) o, mejor, `duration` debe cumplir su contrato: derivar λ de la magnitud para que el asentamiento real sea ≈ `duration` con cualquier cifra — y entonces `onEnd` (línea 189, hoy un temporizador ciego a `delay+duration`) debe dispararse desde la parada real del muelle, no desde un timer. Mientras el canon no cambie, `stats-band` sólo puede paliarlo con `countOptions.duration` por estadística, y eso serían números mágicos que ni siquiera igualan los aterrizajes (la razón 2,21 no depende de `duration`); mal parche. Documentar además en `types.ts:21` / `count-up/README.md:38` que `duration` NO es la duración observable mientras siga siendo un parámetro del muelle.
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
**Re-medición (2026-08-10, servidor limpio)** — ARREGLADA en `acda70f1e`/`89e42735e` y CONFIRMADO por medición contra código actual: las tres cifras de la banda (12.500 · 340 · 48) aterrizan en **2005 ms cada una, con 0 ms de desfase**. El contrato de `duration` («duración REAL del asentamiento para cualquier magnitud, λ derivada del recorrido») se cumple.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-70 — REFUTADO · `feature-grid` · percepcion · BAJA
**Mecanismo** — La reclamación tiene dos mitades y sólo la primera aguanta.
MITAD CIERTA (esto sí es DATA): feature-grid no compone ningún componente de soma con morfo. La raíz coloca Section/Container/Stack (src/uix/blocks/feature-grid/feature-grid.svelte:25-35), la rejilla es un AutoGrid (feature-grid-items.svelte:26), la celda un Motion+Stack (feature-grid-item.svelte:21-25), el chip un Surface (feature-grid-item-icon.svelte:16) y el resto Heading/Text. No hay ningún `runtime.trigger()` en el árbol, luego no hay nada que emitir por los canales de sema — medido y confirmado: cero nodos WebAudio, cero vibrate, cero mutaciones de atributo.
MITAD FALSA (y es la que decide el veredicto): «nada que auditar perceptivamente en él» no se sostiene. El block ES perceptivo por construcción propia, no heredada: feature-grid-items.svelte:32 estampa `data-stagger` y feature-grid-item.svelte:21 envuelve CADA celda en `Motion trigger="viewport"`, así que cada celda se pinta a `opacity: 0` (src/uix/eidos/components/motion/motion.css:18-22) hasta que el IntersectionObserver de motion.svelte:44-61 la ve. Medido en móvil: 4 de 6 celdas a opacity 0 tras la puerta de hidratación, revelándose al hacer scroll con retardos 0/70/140/210/280/350 ms y un fade-in de 240 ms. Eso es exactamente materia de auditoría perceptiva — si el observador no dispara, o la puerta `@media (scripting: enabled)` cae, el usuario ve una sección en blanco; y bajo `prefers-reduced-motion` el preset `fade` es el destino canónico de degradación (base.css:6573-6579), no un fallo. Y la casilla no sólo no está vacía: contiene una lectura bajo umbral. El texto que el propio block elige pintar (feature-grid-item-text.svelte:12, `Text color="muted"`) mide 3,70:1 a 16 px sobre el fondo compuesto en claro y 4,46:1 en oscuro, ambos por debajo del suelo AA de cuerpo (4,5:1). El valor sale del token de canon (`content-muted`), no del block, pero es el block quien elige el rol muted para su descripción — o sea, algo que la reclamación habría cerrado sin mirar. Confianza alta: dos sondas independientes, todas las cifras medidas.
**Evidencia** — URL medida: http://127.0.0.1:5201/blocks/feature-grid/preview?count=6&align=center (también con &mode=dark&dir=rtl&lang=en). Playwright headless desde el scratchpad: probe-A-70.mjs (1280x800), probe-A-70b.mjs (390x640) y probe-A-70c.mjs (contraste claro/oscuro), en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/.
PUERTA DE HIDRATACIÓN: esperado `style#uix-blocks-display`; el HTML del SSR (fetch crudo) NO lo trae (ssr_has_display_style=false) y sí trae 9 `data-animation-pending`, que en el DOM vivo acaban en 0. Ida y vuelta reactivo puramente de cliente: t0 = 4 celdas con `data-animation-pending` y `opacity: 0` (tops 610/790/970/1151 px) → tras hacer scroll, pending=0 y las 6 a opacity 1 con texto. document.visibilityState = 'visible' (pestaña activa; no quedó ningún `[data-animation-pending]` colgado).
EMISIÓN (dos sondas, como se pedía): (a) AudioContext.prototype.createOscillator/createBufferSource parcheados en addInitScript ANTES de cargar → osc=0, buf=0 tras hover + click forzado sobre la celda + Tab×12 + Enter + Space (1 construcción de AudioContext por el arranque de uix, ningún nodo); navigator.vibrate parcheado → 0 llamadas. (b) MutationObserver atado a 8 NODOS concretos (la <section> y cada celda del grid), subtree+attributeOldValue → 0 mutaciones. Ningún `[data-event]`/`[data-event-phase]`/`[data-event-family]` en el subárbol en ninguna variante.
INTERACTIVIDAD: en la <section> del block, 0 coincidencias de a/button/input/select/textarea/summary/[role]/[tabindex]/[href]/[onclick] y 0 elementos con tabIndex>=0 sobre 58 nodos; el recorrido de Tab×12 con teclado real nunca sale de BODY. Idéntico en 390 px + dark + rtl + lang=en (hits=0, focusables=0).
PERCEPCIÓN MEDIDA (lo que la reclamación daba por vacío): stagger resuelto por celda --motion-stagger-index 0..5, --motion-stagger-each 70 ms, animation-delay 0/0,07/0,14/0,21/0,28/0,35 s, animation-name fade-in 0,24 s, data-state=open, data-animation-trigger=viewport. Con reducedMotion:'reduce' emulado no queda ninguna celda invisible (todas llegan a opacity 1); con la pref de app `data-motion='reduce'` en la raíz, opacidades 1 en las 6. Contraste compuesto (color resuelto por canvas, no por regex sobre oklch, y fondo = primer ancestro opaco): ItemText 16 px → 3,70:1 en claro (fg oklch(0.61 0 0)) y 4,46:1 en oscuro (fg oklch(0.5829 0 0) sobre rgb(17,17,17)); ItemTitle 15,88:1 / 16,28:1; glifo blanco sobre el chip solid primary 5,18:1.
### A-71 — CONFIRMADO · `site-footer` · doctrina · BAJA
**Mecanismo** — El arnés pinta el block —que trae sus propios `h2`/`h3`— antes del `h1` de la página: `BlockDemo.svelte:69-73` renderiza el preview y solo en la línea 85 aparece `<Heading level={1}>`. El documento arranca en h2/h3 y el h1 llega el último, en la superficie donde B-8/B-9 piden coherencia. Afecta a las 15 demos por igual: es del arnés, no de un block.
**Evidencia** — `web/routes/blocks/_lib/BlockDemo.svelte:69-73` y `:85`.
**Disposición** — Arnés (app-land, dentro de la frontera): o el h1 va antes del preview, o la identidad de la página encabeza y el block se muestra debajo. Fase 3.
### A-72 — REFUTADO · `contact` · percepcion · BAJA
**Mecanismo** — La mitad factual se sostiene, pero la consecuencia que la convertiría en defecto NO.
1. Es cierto que no hay sustain ni spinner. `src/uix/morfo/components/form.ts:15-100` sólo declara `commit-submit` (sequence:'post'), `signal-warn-invalid` y `commit-reset`: ningún evento de familia `sustain`. Y `src/uix/eidos/components/form/form-submit.svelte:27-45` renderiza el `Form.Submit` de soma en crudo — no compone `Button`, así que no arrastra su prop `loading` ni su `button-spinner.svelte`. Medido: 0 `data-event*` en todo el documento durante los 900 ms, 0 spinners, 0 nodos WebAudio.
2. Pero la ventana NO es muda, y "sólo el cambio de etiqueta" es falso. Medí cuatro canales simultáneos, tres de ellos perceptibles sin tecnología asistiva:
- etiqueta "Enviar mensaje" → "Enviando…" (`state.ts:87-95`, `contact-submit.svelte:22`);
- el botón se apaga de `opacity: 1` a `opacity: 0.4` — el `disabled` que decide `canSubmit` en `contact-submit.svelte:21`;
- `cursor: progress` sobre todo el formulario (`src/uix/eidos/components/form/form.css:68-70`, disparado por el `data-pending` que estampa `src/uix/soma/components/form/form-provider.svelte.ts:201`);
- `aria-busy="true"` en form y submit (`morfo/components/form.ts:82-87` y `:108-115`).
Un cambio de estado doble (texto + 60 % de atenuación) es exactamente la afordancia canónica para una espera sub-segundo; no es "sin canal propio".
3. Los 900 ms no son del block. Salen de `web/routes/blocks/contact/ContactSite.svelte:35-39`, una latencia fabricada por la demo (`uix.timers.schedule(null, 900, …)`). El block no define ventana alguna: dura lo que dure el `onSend` de la app.
4. Y el block no está cerrado a un spinner: `contact-submit.svelte:22` entrega el estado al llamante (`{@render children({ state })}`), que es justo lo que un block puede hacer bajo contrato B — sin morfo y sin `.css` no puede declarar un `sustain` ni pintar un indicador; eso sería canon.
Residuo honesto, que NO sostiene esta reclamación pero merecería hallazgo propio: al deshabilitarse el botón el foco cae a `BODY` durante toda la ventana y la región `aria-live` sigue vacía, de modo que el canal de etiqueta es sólo visual. Eso es un argumento de accesibilidad que la reclamación nunca hace (ella misma lo llama "cambio de etiqueta visible"), y su arreglo viviría en el canon (`Form.Submit`), no aquí.
**Evidencia** — Playwright headless sobre http://127.0.0.1:5201/blocks/contact/preview?challenge=off (script en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-72.mjs y probe-A-72-focus.mjs).
Protocolo: puerta de hidratación esperando `document.getElementById('uix-blocks-display')`; después ida y vuelta reactiva real (tecleo 'J' → value="J", `:placeholder-shown`=false, `[data-field]:focus-within`=true); espera a 0 `[data-animation-pending]`; `visibilityState`="visible". Los tres campos rellenados con `page.keyboard.type()` (no `fill`). Sondas de sema: (a) `AudioContext.prototype.createOscillator`/`createBufferSource` parcheados vía `addInitScript` ANTES de cargar; (b) MutationObserver atado a los NODOS `[data-form-submit]` y `[data-form]` más un observador de documento filtrando `data-event*`.
Muestreo cada ~40 ms, 23 muestras dentro de la ventana (t=46 ms → t=932 ms):
- etiqueta: "Enviar mensaje" (pre) → "Enviando…" (toda la ventana) → "Enviado" (t=963 ms).
- `disabled`: false → true; `opacity` computada del submit: 1 → 0.4.
- `data-pending=""` presente en `[data-form]` Y `[data-form-submit]`; `aria-busy`: "false"→"true" (submit) y ausente→"true" (form).
- `cursor` computado del form: `auto` → `progress` → `auto`.
- `data-event*` en el documento: 0 en las 23 muestras. Primera y única aparición a t=963 ms, ya fuera de la ventana: `announce-pulse-fulfill` sobre FORM (el `commit-submit` con sequence:'post').
- spinners (`[data-button-spinner]`,`[data-spinner]`,`[data-progress]`,`[role=progressbar]`,`[role=status]`): 0 en todas las muestras.
- `document.getAnimations()`: 6 en t=46-160 (transiciones de color de los campos al deshabilitarse), 0 desde t=207 hasta t=932.
- WebAudio en toda la sesión: 1 AudioContext, 2 osciladores, 0 buffer sources — ninguno dentro de la ventana.
Sonda de foco (Enter con el submit enfocado): `activeElement` = BUTTON/submit antes; BODY desde t=102 ms y durante los 900 ms; única región viva del documento = `aria-live="assertive"` con texto vacío en todas las muestras. 0 errores de página.
### A-73 — DATO · `content-section` · percepcion · BAJA
**Mecanismo** — El hecho es cierto y medido, y no hay defecto detrás de él: `content-section` es un block editorial de sólo lectura por construcción, y su composición no incluye ni un solo control.
Cadena: `src/uix/blocks/content-section/content-section.svelte:69-104` monta `<section aria-labelledby>` → `Section` → `Grid`, y dentro sólo `Box` + `Motion` + `Stack` + `Heading` + `Text` (líneas 82-98) y `{@render children?.()}` (103). `content-section-body.svelte:24-28` es `Box` + `Prose` (el HTML lo pone la app). `content-section-media.svelte:37-56` es `Box` + `Motion` + `<figure>` / `<figcaption>` + `Text`, y su propio comentario de cabecera (líneas 8-9) ya lo declara: «Neither element is interactive, so this is document structure — the one a11y surface a block owns (B-8) — not the admission rule firing». No hay ningún `semanticEvent` / `data-event` / morfo en todo el directorio (grep de `sema|semantic|data-event|trigger(` sobre `src/uix/blocks/content-section/` → 0 aciertos de código; sólo prosa de comentarios).
La demo que ancla la reclamación (`web/routes/blocks/content-section/ContentSectionSite.svelte:68-124`) compone `Badge` (soft), `Callout` (`role="note"`, sin descarte — `src/uix/eidos/components/callout/callout.svelte:69` sólo estampa `role`), `AspectRatio` + `Surface`, y dos bloques de HTML crudo (líneas 36-65) que deliberadamente no llevan ningún `<a>`. `Prose` no añade `tabindex` a nada (grep sobre `src/uix/eidos/components/prose/` → 0).
Por qué NO es defecto: lo que sema estampa son eventos de interacción; sin interacción no hay nada que estampar, y la superficie de accesibilidad que sí le toca a un block —la estructura del documento— está presente y correcta (`<section aria-labelledby>` apuntando al `Heading` con `id` en :88, `<figure>`+`<figcaption>` reales en :39-54, `role="note"` del Callout). La ausencia de emisión aquí es el resultado esperado del contrato B, no una omisión. Si algún día la app mete enlaces en su HTML, la interactividad la aporta la app, no el block. Confianza alta: la sonda mide exactamente el hecho reclamado, no un proxy.
**Evidencia** — Sonda Playwright headless (Chromium, 1280x900) en http://127.0.0.1:5201/blocks/content-section/preview, script en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-73.mjs.
Puerta de hidratación cumplida: `<style id="uix-blocks-display">` presente, 57 caracteres; `[data-animation-pending]` = 0 (pestaña activa, rAF corriendo). Ida y vuelta reactivo real: navegación de cliente a `?figure=full` sin recarga (`sameDoc: true`) y el `<figure>` pasó de 528px a 1264px de ancho medido — el runtime de Svelte estaba vivo cuando medí.
Inventario dentro de la región del block (`section[aria-labelledby="s1-title"]`) y en TODO el documento, con el selector focusable completo (a[href], button, input, select, textarea, summary, iframe, object, embed, [contenteditable], [tabindex], audio/video[controls]):
- focusables en la región: 0
- focusables en el documento entero: 0
- `<a>`: 0 · `<button>`: 0 · inputs/select/textarea: 0
- elementos con rol interactivo (button/link/checkbox/radio/switch/tab/menuitem/option/slider/textbox/combobox): 0
- estructura sí presente: `<figure>`: 1 · `[role="note"]`: 1 · 1761 caracteres de texto.
Teclado real: 12 pulsaciones de Tab, cada una seguida de Enter y Space. El foco se quedó en `body` las 12 veces — nunca entró en ningún elemento.
Sema, con las DOS sondas exigidas y ambas armadas ANTES de interactuar:
(a) contador WebAudio parcheado en `AudioContext.prototype.createOscillator` / `createBufferSource` (y `webkitAudioContext`) vía addInitScript: **osc = 0, bufferSource = 0** tras 7 clics (h2, [role=note], figure, figcaption, p, li, blockquote) y el barrido de teclado. Nota incidental: `new AudioContext()` sí se construyó 1 vez durante el arranque, pero no produjo ningún nodo — ningún sonido salió, y eso queda fuera de esta reclamación.
(b) MutationObserver ligado AL NODO `section[aria-labelledby]` (subtree, attributeOldValue) más un segundo observer sobre `document.documentElement`: **0 mutaciones** de `data-event*` / `data-sema*` / `data-state`. Atributos `data-event*` estáticos en el DOM: 0.
Contraste: en la galería `/blocks/content-section` hay 47 focusables, pero son los controles del harness de la demo (la página renderiza el block inline, `hasIframe: false`); ninguno pertenece a la región del block.
### A-74 — CONFIRMADO · `feature-split` · percepcion · BAJA
**Mecanismo** — Las dos patas de la reclamación se sostienen medidas por separado.
(1) Emisión correcta — verificada, no supuesta. `web/routes/blocks/feature-split/FeatureSplitSite.svelte:112-120` compone `FeatureSplit.Actions` (que es un `Group`, `src/uix/blocks/feature-split/feature-split-actions.svelte:9`) con un `Button` en modo `child`, que rinde un `<a>` con `data-archetype=trigger`. Al activarlo (ratón y Enter) el runtime estampa `data-event=contact-activate` / `data-event-family=contact` / `data-event-phase=active` y los limpia al cerrar la ventana de hold, y el canal de sonido crea 2 osciladores por activación. El arco llega íntegro hasta «te he sentido».
(2) Destino inexistente — el `href` se deriva de la prosa. `FeatureSplitSite.svelte:116` escribe `<a href="#{row.eyebrow.toLowerCase()}">` sobre los eyebrows declarados en `FeatureSplitSite.svelte:33,42,51`. Eso produce `#analítica`, `#en el bolsillo` y `#automatizaciones`. Ningún nodo del documento lleva esos `id` ni ese `name`: los únicos `id` del documento son los de infraestructura (`uix-eidos-static`, `uix-eidos-theme`, `uix-blocks-display`, `uix-button-s1..s3`, `svelte-announcer`). El bloque tampoco los podría llevar: `FeatureSplitRowProps` (`src/uix/blocks/feature-split/types.ts:27-37`) es un tipo limpio SIN `HTMLAttributes` y `feature-split-row.svelte:15` no recoge `...rest`, así que la app no puede anclar una fila ni queriendo.
Intenté refutarlo por tres vías y ninguna aguanta:
- «Es el placeholder de siempre»: hay 23 `href="#…"` muertos en 10 demos de blocks, o sea convención de corpus. Pero `#en el bolsillo` no es un placeholder redactado: es copy de pantalla, con espacios, volcado mecánicamente a una URL. Ningún `id` de ninguna app va a ser jamás «en el bolsillo», así que la lectura «ya lo sustituirá la app» no aplica a esta expresión.
- «Sí ocurre algo»: ocurre, y es peor callado — `history.length` sube 2→3 por clic y salta `hashchange`. Invisible para el usuario y deja entradas muertas en el botón «atrás».
- «No es de este block»: el `href` está en el fichero que el auditor ancló, así que la atribución es correcta; lo del `Row` sin `id` es un agravante adicional del block.
Severidad BAJA es la justa: es el único cierre perceptivo que le falta a un block por lo demás presentacional.
**Evidencia** — Sonda Playwright headless (`…/scratchpad/probe-A-74.mjs`) sobre http://127.0.0.1:5201/blocks/feature-split/preview, Chromium 1280x900.
Puertas: `<style id="uix-blocks-display">` presente antes de medir. `[data-animation-pending]` = 6 tras la carga (las filas son `Motion trigger="viewport"`, `feature-split-row.svelte:26,39`); tras barrer la página con rueda real bajan a 0. Ida y vuelta reactivo: Tab real deja `document.activeElement` = `A[href="#analítica"]` con `:focus-visible` verdadero y los atributos que sólo estampa el cliente (`data-button`, `data-archetype=trigger`, `id=uix-button-s1`).
Inventario: exactamente 3 elementos interactivos, los 3 `<a>` de CTA. Resolución de fragmento por elemento (`getElementById(decodeURIComponent(...))` y `[name=...]`):
- «Ver la analítica» → raw `#analítica` → `#anal%C3%ADtica` → targetExists=false, namedTarget=false
- «Configurar alertas» → raw `#en el bolsillo` → `#en%20el%20bolsillo` → false / false
- «Crear una regla» → raw `#automatizaciones` → false / false
`id` presentes en todo el documento: uix-eidos-static, uix-eidos-theme, uix-blocks-display, uix-button-s1, uix-button-s2, uix-button-s3, svelte-announcer.
Sonda (a) WebAudio: `createOscillator`/`createBufferSource` parcheados en addInitScript ANTES de cargar. osc 0 → 2 tras el clic de ratón → 4 tras el Enter (2 por activación); buf 0.
Sonda (b) MutationObserver atado AL NODO (más un segundo observer de árbol): por activación registra data-event=contact-activate, data-event-id=sig-0 (sig-1 en la segunda), data-event-phase=active, data-event-family=contact, y luego los cuatro a null. Sobrevive al cierre del hold.
Consecuencia medida: clic de ratón → url `…/preview` → `…/preview#anal%C3%ADtica`; scrollY 0 → 0 (sin desplazamiento); history.length 2 → 3; hashchange disparado; `document.activeElement` sigue siendo el propio `<a>`, no un destino. Enter en el segundo CTA → url `#anal%C3%ADtica` → `#en%20el%20bolsillo`; scrollY 5 → 5; segundo hashchange. Cero errores y cero avisos de consola.
Alcance del corpus: `grep 'href="#'` en `web/routes/blocks/` da 23 aciertos en 10 demos (banner, contact, cta, faq, feature-split, hero, newsletter, pricing, site-footer, site-header). Sólo medí la resolución de fragmentos en feature-split; en los otros nueve no la he comprobado.
**Disposición** — Se arregla en DOS sitios, y el principal es la demo.
1. DEMO (`web/routes/blocks/feature-split/FeatureSplitSite.svelte:116`) — es donde vive el defecto. Dejar de derivar el fragmento de la prosa: `row.eyebrow.toLowerCase()` genera `#en el bolsillo`, con espacios, para cualquier eyebrow de dos palabras. Dos salidas honradas: (a) darle destino real dentro del propio documento — un `id` por fila, o apuntar las tres al suelo de página de `FeatureSplitSite.svelte:126-136`, que ya existe y no tiene `id`; o (b) si se decide que el CTA de una sección de marketing no tiene a dónde ir en una demo, poner un `href` que lo declare y añadir un slug estable en el array de `rows` (`FeatureSplitSite.svelte:32-57`), nunca la copy en minúsculas.
2. BLOCK / CANON (`src/uix/blocks/feature-split/types.ts:27-37` + `feature-split-row.svelte:15`) — hoy la opción (a) es IMPOSIBLE: `FeatureSplitRowProps` no extiende `HTMLAttributes` y `Row` no recoge `...rest`, así que la app no puede poner `id` (ni `aria-label`/`aria-labelledby`) en una fila. El README justifica el tipo limpio por la colisión del snippet `media` con el atributo HTML homónimo, lo cual no obliga a cerrar `id`: bastaría admitirlo explícito. Decisión de diseño del usuario, no la tomo yo.
3. CORPUS — la misma expresión aparece en `site-footer/SiteFooterSite.svelte:219` (`href="#{label}"`, también copy cruda). Si se toca esto, mirar los 23 `href="#…"` de `web/routes/blocks/` de una pasada en vez de por block.
### A-75 — CONFIRMADO · `pricing` · percepcion · BAJA
**Mecanismo** — El defecto es real y está en la demo, no en el block.
`web/routes/blocks/pricing/PricingSite.svelte:29-46` — `choose(plan, event)` no tiene guarda de idempotencia. En CADA clic asigna `chosen = plan` (línea 31) y, tras `await tick()`, emite incondicionalmente `uix.events?.emit({ name:'commit-select', family:'commit', intent:'affirm' })` (39-45). Si el plan YA era el elegido, la asignación no cambia nada — el rótulo de la línea 144 (`chosen === plan.name ? 'Plan elegido' : plan.cta`) no se recalcula — pero la ceremonia semántica se dispara entera igual: estampa visual `data-event/-family/-intent` durante el hold + grafo de sonido, idénticos al commit verdadero.
Por qué eso es defecto y no criterio: el canon hace exactamente lo contrario en TODOS sus selectores de valor único, y cortocircuita la re-selección redundante ANTES de emitir. `src/uix/soma/components/radio-group/radio-group-provider.svelte.ts:136` → `if (this.opts.value.current === value) return;` (el `runtime.trigger('commit-select')` está en la 144, detrás de la guarda). Igual en `tabs/tabs-provider.svelte.ts:110`, `navigation-menu/navigation-menu-provider.svelte.ts:143` y `:190`, `menubar/menubar-provider.svelte.ts:130` (`if (prev === value) return;`) y `listbox/listbox-provider.svelte.ts:189` (`if (next !== current) { … emit … }`). Cinco componentes, la misma decisión: una re-selección redundante NO es una elección activa y no merece un `commit`. La demo premia un no-op con el mismo `commit`+`affirm` que el canon suprime a propósito, y el `commit` es justo la familia que promete que algo quedó fijado.
Matiz que consideré como refutación y NO se sostiene: sí existe una diferencia de ESTADO (el botón ya dice «Plan elegido»), y la primera pulsación arrastra una mutación extra que la segunda no tiene. Pero eso distingue los dos estados, no las dos pulsaciones: el feedback del gesto es indistinguible, y es el evento —no el rótulo— el que miente sobre la consecuencia. Mi defensa inicial («`affirm` = confirmación suave de una elección activa», docs/CANON.md:100 y :122, así que reafirmar no es mentir) cae contra la práctica uniforme del propio canon.
Confianza alta: medido en navegador, no inferido. Salvedad honesta: conté nodos WebAudio creados (sonda sancionada), no medí la ganancia, así que no afirmo que suene — sí que el grafo se construye idéntico. La estampa visual `data-event*`, que es la que consume eidos, sí es idéntica con certeza.
**Evidencia** — URL medida: http://127.0.0.1:5201/blocks/pricing/preview (servidor ya arrancado, 127.0.0.1). Playwright headless desde el scratchpad: probe-A-75.mjs / probe-A-75b.mjs / probe-A-75c.mjs.
Puertas cumplidas: (1) esperado `style#uix-blocks-display` (lo escribe el $effect de cliente, no el SSR); (2) esperado a 0 nodos `[data-animation-pending]`; (3) ida y vuelta reactiva REAL antes de medir — clic en el switch de facturación: precios `0/29/99 €` → Anual `0/23/79 €` → Mensual `0/29/99 €`. Hidratación probada, no supuesta.
Sondas de emisión, las dos: (a) contador parcheando `AudioContext.prototype.createOscillator`/`createBufferSource` en `addInitScript`, ANTES de cualquier script de página; (b) MutationObserver ligado AL NODO concreto del botón Pro (no a un selector), con `attributeOldValue` + `characterData`.
Números (4 pulsaciones, ids `sig-N` normalizados):
- Pulsación 1 (Pro, primera elección): 15 mutaciones, 4 osciladores. Secuencia: `data-event: null→contact-activate` (sig-2) → `characterData "Plan elegido"` → `data-event: contact-activate→commit-select` (sig-3), `family: contact→commit`, `intent: null→affirm` → limpieza a null.
- Pulsación 2 (Pro YA elegido): 14 mutaciones, 4 osciladores. Misma secuencia EXACTA sin el `characterData`.
- Pulsación 3 (Pro ya elegido otra vez): 14 mutaciones, 4 osciladores. `p2 === p3: true` (byte a byte tras normalizar ids).
- `p1 menos p2` = un solo elemento: `PRO: characterData text="Plan elegido"`. Nada más distingue la elección real de la redundante.
- Pulsación 4 (cambiar a Starter): 16 mutaciones — revierte el rótulo de Pro y estampa el commit en Starter. La discriminación por rótulo sí funciona cuando hay cambio real.
Estado visual del plan elegido vs no elegido (getComputedStyle sobre los nodos reales): el rótulo es el ÚNICO discriminador persistente. `aria-pressed: null`, `aria-current: null`, `data-state: null`, `disabled: false`, `cursor: pointer`, `data-color`/`data-variant` sin cambio en ambos. Las diferencias de fondo que vi (`oklch(0.6406 …)` → `oklch(0.6115 …)`) son la capa de hover con el ratón aparcado tras el clic, no un token de estado elegido.
Contraprueba de canon intentada en vivo: el ToggleGroup de esta misma página resultó deseleccionable (la pulsación «redundante» lo apagó, `data-state: on→off`), así que no sirve de análogo idempotente; la práctica del canon la verifiqué entonces en fuente (radio-group:136, tabs:110, navigation-menu:143/190, menubar:130, listbox:189).
Observación adyacente medida, fuera del alcance de A-75 y que no computo aquí: el `commit-select` pisa el `contact-activate` aún en `phase: active` sobre el mismo nodo (entradas 4-9 del log de la pulsación 1), pese a que el comentario de PricingSite.svelte:33-37 afirma que el `await tick()` evita esa colisión.
No es ninguno de los falsos positivos conocidos (F15-F22, medir-antes-de-hidratar, pestaña en segundo plano).
**Disposición** — DEMO — `web/routes/blocks/pricing/PricingSite.svelte:29-46`. El block está limpio: `src/uix/blocks/pricing/pricing-plan-action.svelte` es un `Box` pelado que no emite nada, y por doctrina un block no tiene morfo ni inventa el resultado de la acción de la app (lo dice el propio comentario en :24-26). Arreglo mínimo, copiando la guarda que el canon ya usa: `if (chosen === plan) return;` como primera línea de `choose()`, espejo de `radio-group-provider.svelte.ts:136` y `tabs-provider.svelte.ts:110`. Si se decide que re-pulsar SÍ debe responder algo (en una app real el CTA llevaría a checkout otra vez), la alternativa canónica no es callar sino degradar el intent: la matriz de deshacer de `docs/CANON.md:118-128` gradúa como `neutral` lo que no es progreso. Cualquiera de las dos es decisión del usuario, no mía. Severidad BAJA declarada: correcta.
### A-76 — DATO · `testimonials` · percepcion · BAJA
**Mecanismo** — El hecho es cierto en las dos mitades y no hay defecto detrás.
(1) Cero afordancia interactiva por construcción. `src/uix/blocks/testimonials/testimonials.svelte:19-27` compone Section·Container·Stack; `testimonials-items.svelte:11-21` un AutoGrid; `testimonials-item.svelte:23-29` un `Motion trigger="viewport"` sobre una `Card`; `testimonials-quote.svelte:9`, `testimonials-author.svelte:15-20`, y los Author\*Name/Role son `Text`/`Group`. Ni un `Button`, ni un `a[href]`, ni un `role`/`tabindex`. Nada que pulsar ⇒ nada que pueda quedarse mudo: el silencio no es una omisión de sema, es la ausencia de emisor.
(2) La Card NO finge afordancia. En `src/uix/eidos/components/card/card.css` TODA la sección «Interactive treatment» está cerrada tras `[data-card][data-interactive]`: `cursor:pointer` + `user-select:none` (línea 172), el `translateY`+`box-shadow` de hover (línea 182), el `background` de hover para `variant='outline'` (línea 191), el `scale` de `:active` (línea 201) y el `outline` de `:focus-visible` (línea 206). `testimonials-item.svelte:12-18` sólo fija `variant='outline'`, `color='neutral'`, `size='lg'`: nunca estampa `interactive`, así que ninguna de esas reglas casa. La `transition` de la línea 64-68 sigue declarada, pero no hay regla que cambie ninguna de las cuatro propiedades ⇒ es una transición sin disparador, no una afordancia.
Único movimiento del block es presencia (entrada `viewport` escalonada vía `data-stagger` en `testimonials-items.svelte:18`), que ocurre una vez al entrar en pantalla y no responde al puntero: no promete pulsación.
Nota de atribución (no altera el veredicto): el demo declara la brecha del carrusel como diferida en `web/routes/blocks/testimonials/+page.svelte:92-95`, o sea que la ausencia de interacción es decisión documentada, no olvido.
Confianza alta: medido, no inferido.
**Evidencia** — Sonda Playwright headless sobre http://127.0.0.1:5201/blocks/testimonials/preview (script en C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-76.mjs), viewport 1280x900, tema base-light.
PUERTA DE HIDRATACIÓN cumplida antes de medir: `style#uix-blocks-display` presente; scroll completo de la página; `[data-animation-pending]` = 0; y prueba de ida y vuelta del cliente — al no haber ningún input en el block, la verifiqué por la escritura que sólo hace el cliente: los 6 envoltorios `Motion` acabaron con `data-animation-trigger=viewport data-animation-style=fade data-state=open` (el IntersectionObserver corrió). 0 errores de consola.
INVENTARIO del `<section>` del block: 59 nodos, 1 `h2`, 5 `[data-card]`. Selector amplio (`button, a[href], a, input, select, textarea, summary, details, [role=button|link|tab|checkbox|switch|menuitem|option], [tabindex], [contenteditable], [data-interactive], label`) ⇒ **0 coincidencias**. 0 atributos `on*` inline. En el SSR crudo, también 0.
FOCO REAL (teclado, no sintético): censo de focusables en TODO el documento = **0**; 6 pulsaciones reales de `Tab` dejan `document.activeElement` en `BODY` las 6 veces. No hay parada de tabulación que pudiera quedar muda.
CARDS: las 5 con `data-variant=outline`, `data-interactive` ausente, `tabindex=null`, `role=null`, `cursor: auto` (no `pointer`), `user-select: auto`, `pointer-events: auto`.
HOVER (afordancia fingida): computado antes vs. después de `hover()` + 500 ms sobre la primera Card — `transform: none → none`, `box-shadow: none → none`, `background: oklch(0.9911 0 0) → oklch(0.9911 0 0)`, `border-top-color: oklch(0.8514 0 0) → oklch(0.8514 0 0)`, `cursor: auto → auto`. Diferencia: **ninguna**. La cita interior: `SPAN` con `cursor: auto`.
EMISIÓN DE SEMA, las DOS sondas exigidas: (a) contador WebAudio parcheado en `AudioContext.prototype.createOscillator`/`createBufferSource` vía `addInitScript` ANTES de cargar la página ⇒ tras click + `Enter` + `Space` sobre la Card: **osc=0, buf=0**; (b) `MutationObserver` atado AL NODO concreto de la Card (attributes + subtree + oldValue), que sobreviviría a su desmontaje ⇒ **0 mutaciones**. Barrido de todo el documento por atributos `data-event*`: **0 nodos**. Coherente: sin emisor no hay emisión, y no hay ningún gesto que quedara sin respuesta.
**Disposición** — Nada que arreglar: no es defecto ni en block ni en canon. Si algún día el block gana la variante carrusel diferida en web/routes/blocks/testimonials/+page.svelte:92-95, ese día sí habrá controles que auditar por emisión.
### A-77 — DATO · `stats-band` · percepcion · BAJA
**Mecanismo** — El hecho es cierto y no constituye defecto — es exactamente el caso «no hay elementos interactivos, luego no emite nada». Cadena: `src/uix/blocks/stats-band/stats-band.svelte:26-47` compone `<section>` → `Section` → `Container` → `AutoGrid data-stagger`, todos cajas; `stats-band-stat.svelte:18-22` envuelve un `Motion trigger="viewport"` sobre `Metrics` (div, `metrics.svelte:68-77`); `stats-band-value.svelte:20-26` rinde `Metrics.Value` (div, `metrics-value.svelte:35`) con `CountUp` dentro (`count-up.svelte:199-207`, `<span data-count-up>`); `stats-band-label.svelte:10-12` un div. Ninguna pieza declara `href`, `onclick`, `tabindex` ni rol interactivo. El ÚNICO emisor de sema alcanzable desde el árbol es el puente `live` del canon `Metrics` (`metrics.svelte:57-63` → `runtime.trigger('signal-notify-update')`), doblemente cerrado: `live = false` por defecto (`metrics.svelte:30`) y `stats-band-value.svelte:20` pasa `value={count}` constante, de modo que el `$effect` comparador de `metrics-value.svelte:21-32` nunca ve un cambio y jamás llama a `notifyUpdate`. Por tanto no existe ningún trigger que la cascada de sema pueda estampar: la ausencia de `data-event` no es un fallo de estampado, es la consecuencia de que no hay evento. Y la consecuencia perceptiva que quedaría por cubrir sí está cubierta: la superficie del block es la ENTRADA (índice estructural `data-stagger` + `Motion` viewport + `CountUp`), y la medí funcionando. Confianza alta: leído y medido. Matiz sin efecto sobre el veredicto: `StatsBandStatProps = MetricsProps` (`types.ts:28`), así que una app PUEDE pasar `live` y entonces sí habría emisión — es superficie del canon abierta a la app, no del block tal como se compone y se demuestra.
**Evidencia** — Sonda Playwright headless (scratchpad/probe-A-77.mjs) contra http://127.0.0.1:5201/blocks/stats-band/preview. Puerta de hidratación: esperado `#uix-blocks-display` (nodo cliente) y luego 0 `[data-animation-pending]`. Ida y vuelta real (no hay inputs en la página, así que usé el comportamiento sólo-cliente): los 3 `[data-count-up-value]` pasan de la semilla SSR "0","0","0" a "12.050","328","46" a los 2,6 s — hidratado y rAF vivo en primer plano. Mapa de la banda (25 nodos bajo el `<section>` raíz): elementos interactivos = 0 (selector a[href],button,input,select,textarea,summary,details,[tabindex],[role=button|link|tab|checkbox|menuitem],[onclick],[contenteditable]); focusables = 0; `cursor:pointer` = 0; `[data-metrics][data-live]` = 0 de 4 `[data-metrics]`. Atributos data presentes: sólo estructurales/visuales (data-section, data-container, data-box, data-auto-grid, data-grid, data-stagger, data-metrics(-value|-label), data-count-up(-value), data-animation-style, data-animation-trigger, data-state, data-align, data-layout, data-size) — ni un data-event. Interacción: hover + click + dblclick sobre la banda y sobre los 4 stats, sus values y sus labels; 12 pulsaciones Tab (document.activeElement se quedó en BODY las 12 veces, `inBand:false`), más Enter y Space. Resultado con las DOS sondas: (a) contador WebAudio parcheado en addInitScript ANTES de la carga → createOscillator = 0, createBufferSource = 0 (1 AudioContext construido, sin nodos); (b) MutationObserver atado AL NODO de la banda (+ uno al documento) → 0 mutaciones `data-event*`, y 0 elementos con `[data-event*]` al final. Contra-prueba de que la sonda no es ciega: el mismo observer en /blocks/stats-band, al pulsar un Button real, capturó 10 mutaciones — `data-event=commit-toggle`, `data-event-family=commit`, `data-event-intent=neutral`, `data-event-phase=active`, `data-event-id=sig-0` y su retirada. 0 errores de consola.
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
### A-78 — ARREGLADO · `stats-band` · doctrina · BAJA
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — `stats-band.svelte:26` emite `<section {...rest}>` sin nombre accesible propio y el block no pone ningún encabezado dentro (no importa `Heading`). Un `<section>` sin nombre no se expone como landmark, así que la banda no es navegable ni por landmarks ni por titulares, y el README no dice nada de ninguna de las dos cosas.
**Evidencia** — `src/uix/blocks/stats-band/stats-band.svelte:10,26,47` y el grep de landmark = 0 en su README.
**Disposición** — Documentar la posición (una banda de cifras puede legítimamente no ser landmark) y, si se quiere región, que el app pueda nombrarla por `...rest`. Fase 3, junto a A-22.
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
**Re-medición (2026-08-10)** — Las dos mitades de la disposición están cumplidas: el README declara la posición en su párrafo `**Landmark + headings**` (una banda de cifras es una tira de apoyo, deliberadamente ausente del árbol de landmarks y del outline) y `stats-band.svelte:26` derrama `...rest` sobre el `<section>`, así que una app que quiera región la nombra con `aria-label`. Cerrada junto a A-22.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-79 — CONFIRMADO · `site-footer` · percepcion · SOSPECHA
**Mecanismo** — El selector del pie (`web/routes/blocks/site-footer/SiteFooterSite.svelte:156-192`) es un `Select` canónico cuyo `onValueChange` (líneas 162-166) escribe `uix.prefs.setIntent('language', …)`. El morfo de Select (`src/uix/morfo/components/select.ts:15-24`) declara para el ítem `commit-select` con `family:'commit'`, `intent:'affirm'`, y nada más: la elección de un ítem es lo único que el componente sabe. La CONSECUENCIA — que cambia el régimen de la app entera — es app-land, y la demo no la enuncia: no hay ninguna emisión adicional. Medido en navegador: al elegir «English» se estampan exactamente `emerge-open` (trigger), `commit-select`+`affirm` (ítem) y `emerge-close`; cero eventos de familia `shift`. Y el régimen SÍ cambia: `<html lang>` pasa es→en y el texto del framework se retraduce en vivo. Que eso es un defecto y no una opinión lo fija el propio corpus: (1) `docs/CANON.md:77` da a `shift` la pregunta «¿cambió el contexto / régimen?», su verbo `context` (línea 196) y la regla de composición «shift must orient the context change (focus + title + landmark)» (línea 250); (2) `src/uix/morfo/components/field-langs.ts:39-54` y su comentario de `Menu` (líneas 141-145) declaran que cambiar de idioma es `shift-navigate` y RECHAZAN explícitamente las semánticas de commit del Select para ese gesto («no `Select` commit semantics leak into the field»), apilando ambos niveles cuando el commit del control también ocurre; (3) el block hermano `web/routes/blocks/pricing/PricingSite.svelte:21-45` documenta el patrón exacto que falta aquí — «lo que NINGUNA referencia puede expresar es la CONSECUENCIA, así que el app emite» vía `uix.events?.emit({...})` tras un `tick()` para no colisionar con la proyección del contacto. La demo tiene la API, el patrón y un precedente en su misma galería, y aun así el cambio de régimen suena y se estampa como elegir un topping de pizza. Matiz que NO refuta: la reclamación menciona «dirección», y con este catálogo (es/en/fr/de, todos LTR) `dir` no se mueve — la dimensión `direction` sí deriva de `language` (`src/arts/prefs/dimensions/direction.ts:30`), así que con un idioma RTL voltearía; el defecto se sostiene solo con `lang` + `langs`. Tampoco se sostiene la salida «emitir shift sería inerte»: `SEMA_MAP.families.shift` (`src/uix/sema/sema-map.ts:171-186`) tiene firma de sonido propia (pitch 500, decay 220, duration 160), distinta de la del commit, y esta galería arranca con `sound: true` (`web/routes/blocks/_lib/BootUix.svelte:63`).
**Evidencia** — Playwright headless sobre http://127.0.0.1:5201/blocks/site-footer/preview (scripts en …/scratchpad/probe-A-79.mjs y probe-A-79b.mjs). Puerta de hidratación: espera a `style#uix-blocks-display` + a que no queden `[data-animation-pending]`; ida y vuelta reactivo real con `page.keyboard.type()` (valor del input = "no-es-un-correo") y `aria-expanded` del trigger false→true al abrir. Sondas de sema instaladas ANTES de interactuar: (a) contador WebAudio parcheando `AudioContext.prototype.createOscillator`/`createBufferSource` → 6 osciladores, 0 buffers en la secuencia abrir+elegir (o sea: suena); (b) MutationObserver ligado AL NODO del ítem `[data-select-item][data-value="en"]` (sobrevive al desmontaje del portal) + observador de documento sobre `data-event*`. Estampas capturadas, completas: trigger `emerge-open/emerge/—`; ítem `commit-select/commit/affirm/phase=active`; trigger `emerge-close/emerge/—`. Ninguna con `data-event-family="shift"`. Régimen antes/después: `<html lang>` "es"→"en", `dir` "ltr"→"ltr"; diff del innerText del body = 2 líneas de 36, ambas del framework: «Debe ser una dirección de correo válida»→«Must be a valid email address» y la etiqueta del trigger «Español»→«English». Orientación tras el cambio: `activeElement` sigue siendo el mismo `button[data-select-trigger]`, `document.title` vacío, sin ningún otro movimiento — no se cumple la regla de orientación de CANON §8.5. No es ninguno de los falsos positivos F15–F22 (el F22 «un Select controlado muestra el valor crudo» convive en este mismo fichero, anotado en las líneas 169-175, pero es otra reclamación).
**Disposición** — Se arregla en la DEMO (app-land), no en el canon: `Select` hace bien en emitir `commit-select`+`affirm` — no puede saber que esta lista es el eje de idioma de la app. En `web/routes/blocks/site-footer/SiteFooterSite.svelte:162-166`, tras el `setIntent`, emitir el nivel superior con el patrón ya documentado en `web/routes/blocks/pricing/PricingSite.svelte:38-45`: `await tick()` y luego `uix.events?.emit({ target, name: 'shift-context', family: 'shift', message: language })` (verbo `context` de `docs/CANON.md:196`; `shift` no lleva intent). El `tick()` no es cosmético: evita que la proyección del `commit-select` del ítem y la del shift se pisen en el mismo turno. Si además se quiere cumplir CANON §8.5 («shift must orient»), la demo debería orientar el cambio — devolver el foco al trigger ya reetiquetado basta como mínimo, y sería el sitio natural para un anuncio en vivo. Nota para Fase 5: `blocks:check` no puede ver esto (el block no tiene morfo); si se quiere guardia, la regla es «demo que escribe en `uix.prefs` debe emitir un `shift`».
### A-80 — ARREGLADO · `faq` · sin dim · —
**Mecanismo** — `Faq.Item` deja de reimplementar la autogeneración del `value`; la hace el canon. README, tipo y demo dejan de atribuírsela.
**Evidencia** — Verificado en navegador tras el cambio: 5 ítems, abre y cierra, `aria-expanded` false→true y contenido `data-state=open`.
### A-81 — ARREGLADO · `cta` · sin dim · —
**Mecanismo** — El ejemplo de `cta/index.ts` deja de anunciar el `variant` que se cortó, y explica por qué no existe.
**Evidencia** — `cta/index.ts`.
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
### A-82 — ARREGLADO · `cta` · sin dim · —
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — El block EXPORTA `CtaLayout` (`index.ts:21`) y la demo lo redeclara a mano en tres sitios en vez de importarlo. Mismo patrón que A-11, agravado porque aquí el tipo canónico es del propio block.
**Evidencia** — `web/routes/blocks/cta/+page.svelte:15,157`, `CtaSite.svelte:18`, `preview/+page.svelte:10`.
**Disposición** — Importar `CtaLayout`. Fase 3, junto a A-11.
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
**Re-medición (2026-08-10)** — La demo ya no redeclara nada: `web/routes/blocks/cta/+page.svelte:14` y `CtaSite.svelte:18` importan `CtaLayout` de `$blocks/cta`.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-83 — ARREGLADO · `hero` · sin dim · —
**Mecanismo** — Ver A-02: el estado «la copia va sobre sólido» deja de aplicarse pieza a pieza y pasa a cubrir el racimo entero.
**Evidencia** — Ver A-02.
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
### A-84 — ARREGLADO · `feature-grid` · sin dim · —
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — El block declara un `align` de sección y solo lo aplica al `Stack` raíz (`feature-grid.svelte:30`); su propio tipo instruye al app a repetirlo: «Set the matching alignment on the header's own `Heading`/`Text` and on each `Item`» (`types.ts:22-25`), y la demo lo repite. No es «inventarle estado a un block de layout» —el eje YA está declarado—: es un eje declarado a medias, con cada consumidor re-derivándolo en el punto de uso. El propio tier tiene el precedente resuelto: `team` pasa su `align` a las partes por contexto.
**Evidencia** — `src/uix/blocks/feature-grid/types.ts:22-26`, `feature-grid.svelte:19,30`, `web/routes/blocks/feature-grid/FeatureGridSite.svelte:42,45`.
**Disposición** — Que `.Header` y `.Item` lean el `align` por contexto, como `team`. Fase 3.
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
**Arreglo (2026-08-10)** — ARREGLADO 2026-08-10 y MEDIDO. `feature-grid/context.ts` nuevo (mismo patrón y misma regla que `team`: es un DEFAULT, un `align` explícito en una parte gana), el root lo publica con un getter, y `.Header` y `.Item` lo leen. El typedoc que instruía al app a repetir el eje —«set the matching alignment on the header's own Heading/Text and on each Item»— se sustituye por la verdad: se pone una vez en la raíz. Medido en `/blocks/feature-grid/preview`: con `align=center` el `align-items` computado es `center` en la cabecera Y en las celdas; con `align=start`, `start` en ambas. `FeatureGridHeaderProps` gana el `align` (era `= BoxProps` y `svelte-check` cazó la falta como +1 error sobre base).
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
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
### A-85 — ARREGLADO · `site-header` · sin dim · —
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — El `aria-label="Abrir navegación"` que la demo escribe en `web/routes/blocks/site-header/SiteHeaderSite.svelte:161` (dentro del `child` de `Drawer.Trigger`) SÍ llega al Button y SÍ se pinta en el DOM — y acto seguido lo pisa el canon.
Cadena exacta:
1. La demo escribe el atributo DESPUÉS del spread (`<Button {...props} … aria-label="Abrir navegación">`), así que gana en el objeto de props: eidos Button lo pasa en `headlessProps` (`src/uix/eidos/components/button/button.svelte:42,113`), soma Button lo desestructura (`src/uix/soma/components/button/components/button.svelte:21,42`) y lo alimenta al morfo (`src/uix/morfo/components/button.ts:164-169`, condicional `prop-truthy`).
2. El MISMO nodo `<button>` está registrado como parte de DOS morfos con `syncAttrs: true`: `ButtonProvider` (`src/uix/soma/components/button/button-provider.svelte.ts:111-116`) y `DrawerTriggerProvider` (`src/uix/soma/components/drawer/drawer-provider.svelte.ts:412-420`).
3. `syncPartAttrs` (`src/uix/soma/runtime.svelte.ts:502-521`) escribe los atributos compilados de cada parte de forma imperativa con `dom.apply` dentro de un `$effect`. No hay fusión: gana el último que escribe.
4. El morfo del trigger del cajón declara `aria-label` INCONDICIONAL con `v.translationRef('#?components.drawer.trigger|Open drawer')` (`src/uix/morfo/components/drawer.ts:181-185`, espejado igual de incondicional en `drawer-provider.svelte.ts:444`). Su efecto corre después del del Button y sobrescribe con la traducción genérica.
Resultado: el nombre accesible final es «Abrir cajón», la etiqueta genérica del canon. Y no es esquivable desde el sitio de llamada: poner el `aria-label` en `Drawer.Trigger` tampoco sirve, porque `drawer-trigger.svelte:26` hace `mergeProps(restProps, state.props)` y `state.props` (segundo argumento) gana también. El consumidor NO tiene ninguna vía para nombrar ese botón.
**Evidencia** — URL medida: http://127.0.0.1:5201/blocks/site-header/preview (viewport 390x844, Chromium/Playwright headless). Puerta de hidratación: espera a `document.getElementById('uix-blocks-display')` (efecto de cliente, ausente en SSR) + espera a 0 `[data-animation-pending]` + ida y vuelta reactiva real (clic → `data-state` pasa de `closed` a `open`).
1. SSR crudo (fetch sin JS) del `<button data-drawer-trigger>`: SIN `aria-label` (ni `type`, ni `aria-controls`, ni `role`) — prueba de que esos atributos los estampa `syncAttrs` en el cliente.
2. MutationObserver instalado ANTES de la hidratación (`addInitScript`, `attributeFilter:['aria-label']`, `attributeOldValue`) sobre el árbol, filtrando por el nodo del trigger. Log completo, 2 escrituras en el mismo tick (t≈3450 ms):
- `{old: null → "Abrir navegación"}`
- `{old: "Abrir navegación" → "Abrir cajón"}`
Es decir: el Button escribe la etiqueta de la demo y el Drawer la pisa inmediatamente. Tras interactuar (clic) hay una tercera escritura re-afirmando «Abrir cajón».
3. Atributo final en el DOM (cerrado y abierto): `aria-label="Abrir cajón"`; el botón no tiene texto visible (`textContent` vacío, sólo `Icon.Menu`), así que el aria-label ES el nombre.
4. Cómputo REAL del nombre accesible vía CDP `Accessibility.getPartialAXTree`: `AX role: button | AX name: "Abrir cajón" | ignored: false`, fuente `attribute aria-label "Abrir cajón"`. Recuento por rol+nombre: `getByRole('button', {name:'Abrir navegación'})` = 0; `getByRole('button', {name:'Abrir cajón'})` = 1.
Scripts: C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/65ab52b0-378f-44bf-85bf-2c9250e2882d/scratchpad/probe-A-85.mjs, probe-A-85b.mjs, probe-A-85c.mjs
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
**RESOLUCIÓN (2026-08-11)** — Arreglado en el CANON, y no con el mecanismo que
esta disposición prescribía: replicar el `prop-truthy` de button era anotar 132
morfos y hacer dar al texto del consumidor una vuelta entera por el grafo
reactivo. La declaración del drawer era CORRECTA; la precedencia del runtime era
el defecto. Regla de VOCABULARIO (dos clases de precedencia, `morfo.md` Step 4):
`aria-label` ∈ `ARIA_NAMING_ATTRS` → el compilador marca el plan `consumerWins`,
el atributo viaja en el render bag de la parte (nunca por `dom.apply`, que era
la escritura post-render que pisaba) y `mergeProps` lo resuelve consumer-first.
Los espejos manuales del provider del drawer (líneas 440/1266) retirados.
Re-medido en `/blocks/site-header/preview` (2026-08-11): el SSR crudo YA trae
`aria-label="Abrir navegación"` (antes: sin atributo — los naming ships in the
bag también arreglan el hueco de SSR), y tras hidratación + click round-trip
(`data-state` closed→open→…) el label del consumidor sobrevive — la segunda y
tercera escritura del MutationObserver de la evidencia ya no existen. El caso
default (sin label del consumidor, `/uix/components/drawer`): «Abrir cajón» del
catálogo. Guard: `runtime.svelte.test.ts` («ships naming defaults in the bag»),
`props.test.ts` (política consumer-first), `compile.test.ts` (clasificación).
**Disposición ORIGINAL (superada)** — Se arregla en el CANON, no en el block (la demo compone correctamente y el block `site-header` sólo renderiza el snippet `mobileTrigger`). Dos puntos posibles, por orden de limpieza:
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
1. `src/uix/morfo/components/drawer.ts:181-185` + `src/uix/soma/components/drawer/drawer-provider.svelte.ts:444`: el `aria-label` del trigger debe ser un DEFAULT, no una imposición — condicionarlo a que el consumidor no haya aportado uno (patrón `prop-truthy` sobre un `aria-label` propio del trigger, como ya hace `src/uix/morfo/components/button.ts:164-169`), y en `drawer-trigger.svelte:26` dejar que el valor del consumidor gane sobre el del canon.
2. `src/uix/soma/runtime.svelte.ts:502-521`: el caso general —dos partes de morfos distintos con `syncAttrs: true` sobre el MISMO nodo (composición `child`/asChild)— es un pisotón silencioso de último-escritor-gana. Merece al menos un aviso en dev cuando dos partes reclaman el mismo atributo del mismo nodo con valores distintos; hoy no se detecta ni en `blocks:check` ni en el lint de eidos.
Paliativo si no se toca el canon: la demo no puede arreglarlo por sí sola (ninguna de las dos vías de llamada gana), así que documentar la limitación en `src/uix/blocks/site-header/README.md` sería lo único disponible.
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
**Re-medición (2026-08-10, servidor limpio)** — ARREGLADA. El trigger del cajón expone hoy `aria-label="Abrir navegación"` — el valor del CONSUMIDOR — donde antes el canon imponía «Abrir cajón». Medido en el DOM servido.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
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
### A-86 — ARREGLADO · `pricing` · sin dim · —
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — `PricingSwitchProps` declara las tres cadenas como REQUERIDAS (`monthlyLabel`, `annualLabel`, `'aria-label'`, sin `?`) y el ejemplo de la puerta de entrada monta `<Pricing.Switch annualLabel="Anual (−20%)" />`: faltan dos props obligatorias, no compila.
**Evidencia** — `src/uix/blocks/pricing/types.ts:34-41` y `src/uix/blocks/pricing/index.ts:11`.
**Disposición** — Corregir el ejemplo. Fase 3, junto a A-39 y A-81.
### A-87 — ARREGLADO · `contact` · sin dim · —
**Mecanismo** — Duplicado de A-44, cerrado con él.
**Evidencia** — Ver A-44.
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
### A-88 — ARREGLADO · `team` · sin dim · —
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Mecanismo** — La cadena de cajas es: `AutoGrid` (grid, `align-items: stretch` por defecto) → el `<div>` que pinta `Motion` (`src/uix/eidos/components/motion/motion.svelte:119-129`, un div PELADO, sin `display` ni `height` propios) → el `Stack` del miembro (`src/uix/blocks/team/team-member.svelte:27-31`) → el `Group` de enlaces con `marginTop='auto'` (`src/uix/blocks/team/team-member-links.svelte:18`). El `stretch` de la retícula sí estira el div del `Motion` hasta la altura de la fila, pero ese div es `display: block`, así que el `Stack` de dentro —que es quien tiene el `display:flex; flex-direction:column` -- se queda en altura de contenido (`height: auto`). En una columna flex de altura automática el espacio libre es CERO, y `margin-top: auto` reparte cero. El token llega bien (`--box-margin-top: auto` vía `Box`, `box.css:242` + `formatLayoutSpace` que deja pasar la cadena `auto`), pero su valor usado se resuelve a `0px`. Resultado: la fila de enlaces cuelga justo detrás de la biografía y, con biografías de distinto largo —el caso EXACTO que el README declara como razón de ser de la regla (`src/uix/blocks/team/README.md:19` y `:69-71`)—, las filas de iconos quedan a alturas distintas dentro de la misma fila de la retícula. La reclamación acierta en el mecanismo Y en la consecuencia; también deja al README afirmando algo que el DOM no cumple.
**Evidencia** — Playwright headless (script en `…/scratchpad/probe-A-88.mjs` y `probe-A-88b.mjs`), Chromium 1280x900, sobre http://127.0.0.1:5201/blocks/team/preview?align=center&columns=4&bio=true. Puerta de hidratación: esperado `#uix-blocks-display` (presente), scroll completo y 0 nodos `[data-animation-pending]`; 7 `[data-animation-trigger=viewport][data-state=open]` estampados por el observer del cliente (prueba de que el JS corrió, no medí el SSR estático).
TAL COMO SE SIRVE (bio=true): retícula `align-items: stretch`, columnas 224px x4. Celdas = div del `Motion` con `display: block`. Fila 1: Ada cellH=288 / stackH=287.8; Grace, Alan, Katherine cellH=288 / stackH=267.5 → 20.3px sin absorber. `getComputedStyle([data-group]).marginTop = 0px` en las SEIS tarjetas, pese a `--box-margin-top: auto`. `linksTop` fila 1 = [462, 442, 442, 442] → desviación 20px; fila 2 = [741, 762] → 21px. Captura `A-88-shipped2.png`: los iconos de Ada y de Barbara caen visiblemente más bajos que los de sus vecinas.
CONTRAFACTUAL A (div del `Motion` a `display:flex; flex-direction:column` + `Stack` con `flex:1 1 auto`): `marginTop` pasa a 20.30px y `linksTop` = [462,462,462,462] y [762,762] → desviación 0px.
CONTRAFACTUAL B (sólo `[data-stack] { height: 100% }` dentro de la celda): mismo resultado, desviación 0px (captura `A-88-fixed.png`).
LÍNEA BASE sin biografía: todas las tarjetas 195px, desviación 0px — es decir, la regla efectivamente «no cambia nada» ahí, que es lo único que el README acierta.
**Disposición** — Se arregla en el BLOCK, no en el canon: el defecto nace de la propia composición de `src/uix/blocks/team/team-member.svelte` (un `Stack` de altura automática dentro del div estirado del `Motion`). El arreglo mínimo verificado es que el `Stack` del miembro llene la celda —`height="100%"` en la línea 28 de `team-member.svelte`— o, equivalente, que el envoltorio del `Motion` sea columna flex. Nota de canon: `Motion` (`motion.svelte:119`) no expone ni `display` ni `height`, sólo derrama `...rest`, así que si se prefiere arreglarlo en el envoltorio hay que darle ese asa en el canon. Y corregir de paso el README del block (`README.md:19` y `:69-71`), que hoy documenta un anclaje que no ocurre.
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
**Arreglo (2026-08-10)** — ARREGLADO 2026-08-10 y MEDIDO. `team-member.svelte:28` pasa `height="100%"` al `Stack` (prop pública heredada de `BoxProps`, sobrevive el `Omit` de `StackProps` — comprobado en el contrato antes de dar el cambio por bueno). Medido en `/blocks/team/preview?align=center&columns=4&bio=true`: `linksTop` = [274,274,274,274], **desviación 0 px** (era 20), y `margin-top` reparte **20,297 px** donde antes daba `0px` en las seis tarjetas — el mismo valor que el contrafactual A de esta ficha, medido en su día por otra vía. El README del block, que afirmaba el anclaje dos veces, pasa a decir la verdad en vez de tener que corregirse.
fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar La auditoria del tier tenia los veredictos desemparejados de sus hallazgos: un join por POSICION, y el journal del workflow era de sesion. Reproducido en vivo al re-verificar — el journal devuelve los resultados en otro orden que la entrada. Ledger nuevo con 90 ids estables (AUDIT-blocks-ledger.md), union SIEMPRE por id: 41 arreglados, 34 confirmados, 12 refutados con motivo escrito, 3 dato. La tasa real de refutacion es del 13%, no del 28%. De las 12 ALTA de percepcion, 8 tenian la causa raiz en el CANON. Los blocks componen bien; lo que estaba roto era el arco perceptivo. CANON - Link: la prop color era inerte en subtle/plain (inherit a 0-2-0 ganaba a la paleta a 0-1-0), el hover clavaba primary y el active quedaba tapado por el hover de variante. Medido: el enlace del footer pasa de la tinta del padre a la suya. - Card: prometia BoxProps y no los aplicaba — height="100%" era un atributo inerte. El tipo dice la verdad y el eje de tamano se aplica con un helper compartido (buildSizeStyle). Tarjetas al fin de igual alto. - Form.Submit / Form.Reset: componen el Button del canon, asi que la accion principal de un formulario recupera el contact-activate en el gesto. Y el aria-label generico deja de pisar el texto propio (WCAG 2.5.3 para todo consumidor), verificado con el AX tree de Chrome. - Field: dejaba de duplicar commit-submit dentro de un Form — un Enter emitia dos commits con intents contradictorios y dos earcons. - NavigationMenu: commit-select se mueve al Link (navegar es el acto evaluable), el despliegue habla como emerge-open/close en vez de fingir una seleccion por hover, y el pack casa por fin con la parte que recibe el estampado — antes sonaba a la ganancia base, 10x lo disenado. - Badge: el boton de quitar compone IconButton; la altura del chip pasa a ser la del control, asi que md significa lo mismo en todo el sistema. - Fundacion: [data-on] arrastra la propiedad color, no solo las variables. BLOCKS - hero: la CTA secundaria pasa de 1.61:1 a 6.61:1. - contact: separa incomplete de invalid — el camino de error por campo era inalcanzable por construccion — y su frase llega a la AT. - newsletter: coordina (fase 4 del plan). Maquina de cinco estados, palabras propias, Submit y Reason como partes que leen el contexto. - site-header ya no congela la pagina al cruzar el breakpoint; site-footer emite su commit; pricing no se vacia; cta llega a sangre de verdad. DOCTRINA - La frontera dura 1 nombra $libs/forms como puerta sancionada. - El contrato B admite un segundo servicio: el anunciador. Un block que posee las palabras de sus estados tiene que poder decirlas. Gates: blocks:check 15/0 · vitest 20/20 en blocks y 402/402 en eidos+blocks · svelte-check 75/54 (linea base) · docs:check 0/0 · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-89 — REFUTADO · `contact` · sin dim · —
**Mecanismo** — Las dos mitades literales son ciertas (el block escribe `sent = true` y su máquina no tiene arista de vuelta), pero la consecuencia que las convierte en defecto —«la sección ya no puede volver a enviar NUNCA»— es falsa: `sent` es un prop **`$bindable`** público (`contact.svelte:43` `sent = $bindable(false)`, `types.ts:46`), así que el app decide cuándo la sección vuelve a estar disponible, igual que decide cuándo se envió. Que la vuelta sea del producto y no del block es coherente con la doctrina: el block coordina su sección, no gobierna el ciclo de vida del envío.
**Evidencia** — `src/uix/blocks/contact/contact.svelte:43,62,85` y `src/uix/blocks/contact/types.ts:46`.
**Disposición** — No se toca el comportamiento. Queda un asunto menor y distinto: el ejemplo de uso no enseña la costura de vuelta (`bind:sent`) — si se roza el fichero, se añade.
### A-90 — ARREGLADO · `contact` · sin dim · —
**Mecanismo** — `ContactVerification` es ahora `ProofOfHumanStatus` importado del canon, no una copia de las mismas cuatro palabras.
**Evidencia** — `contact/state.ts`.
blocks(background): seis fondos, y el que parecía mejor era el que no se podía leer F5 de PLAN-background.md: los seis blocks de D-BG.10 (2) reciben `Background.*`. El hero cayó en F2 y la página compuesta ya existía — no había que construirla, había que verificarla con la decoración puesta. La forma es uniforme y tiene un solo desvío: cada block gana un prop `decor` que monta `<Background.Pattern>` como HIJA de su `Section`, la misma composición que el hero ya probó. El desvío es `cta`, donde la decoración va dentro del PANEL, que es donde está el ojo. **Dos hallazgos que sólo la medición podía dar.** Dentro del panel sólido del `cta`, las dos tramas TINTADAS hunden el titular bajo AA: `glow` lleva la copia blanca de 5,18:1 a **3,06:1** y `mesh` a **2,27:1**. Pintan un lavado claro anclado en `50% 0%` — exactamente donde se asienta el titular. Las de línea toman la tinta de regla, oscurecen, y la copia sube a **14,35:1** mientras la textura sigue leyéndose a 3,19:1 contra el panel. Por eso el default es `rings`, que además irradia del mismo anclaje superior que usaba el glow. Las tintadas se ofrecen igual: el app decide, pero ya con el número delante, en el tipo y en el README. Y una trama tintada dentro del panel era invisible por construcción: `--_background-tint` cae en `--color-primary-solid`, que es EL MISMO valor que pinta el panel — un halo del color del panel sobre el color del panel. El block pasa ahora `color="var(--color-content-on-solid)"`, el token que ya usa para su propia copia; medido después, el tinte resuelve a `#ffffff`. **Los defaults, y por qué dos están apagados.** `cta` `rings` · `stats-band` `grid`, porque una banda de cifras se lee como medida y la rejilla es la trama que lo dice sin decorar por decorar · `testimonials` y `feature-split` `glow`. `site-footer` y `banner` reciben la capacidad APAGADA: la columna B del plan de calidad no pide acabado en ninguno de los dos, y encenderlo por nuestra cuenta sería inventar un aspecto que nadie pidió. **Un desvío de alcance, declarado.** La columna B pedía «`Backdrop` alterno» POR FILA en `feature-split`; se resuelve en la SECCIÓN. Una banda por fila obligaría a cada `Row` a poseer el estado de alternancia, y eso es coordinación — un block que coordina deja de ser un block que no posee nada. Medido en Chrome sobre la página compuesta real: cinco pilas, los cinco anfitriones adoptados (`position: relative` + `isolation: isolate`) sin que ningún block los posicione; la copia mide 15,88:1 sobre las secciones decoradas y 5,18:1 sobre el panel del cta — AA en todas. Sobre lienzo oscuro, medido aparte: 17,06 → 13,23:1 con `glow` y 12,37:1 con `grid`/`dots`. Ledger: seis filas nuevas, A-100…A-105, cada una con su mecanismo y su evidencia. Gates: `blocks:check` 0/18 · `rtl:check` 0/180 · `docs:check` 0/0 en 634 docs · 434/435 (el fallo es el `skin-media-player` de siempre) · `check` 0 errores propios · prettier limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### A-100 — ARREGLADO · `cta` · percepcion · ALTA
**Reclamación** — la decoración del panel, encendida sin medir, deja el titular
bajo AA.
**Mecanismo** — el panel es `Surface variant='solid'` con la copia en tinta
on-solid (blanca). `glow` y `mesh` son las dos tramas que pintan con el TINTE, y
lo hacen sobre un área grande anclada en `50% 0%` — exactamente donde se asienta
el titular. El lavado claro sube la luminancia del fondo bajo la copia.
**Evidencia** — medido en Chrome sobre panel `primary` (`142,78,198`) con tinta
`#ffffff`: llano **5,18:1** · con `glow` **3,06:1** · con `mesh` **2,27:1** ·
con cualquiera de línea (`grid`/`dots`/`lines`/`rings`) **14,35:1** · con
`vignette` **9,89:1**. La textura de línea sigue siendo visible: la regla mide
3,19:1 contra el panel, que es el registro de una decoración.
**Arreglo** — el default pasa a `rings`, que irradia del mismo anclaje superior
que usaba el glow. Las tintadas se siguen ofreciendo, con el número escrito en
el tipo y en el README: el app decide, pero informado.
### A-101 — ARREGLADO · `cta` · doctrina · MEDIA
**Reclamación** — la trama tintada dentro del panel es invisible.
**Mecanismo** — `--_background-tint` cae por defecto en `--color-primary-solid`,
que es el MISMO valor que pinta el panel cuando `color='primary'`: un halo del
color del panel sobre el color del panel.
**Evidencia** — medido: `--_background-tint` resolvía a
`oklch(0.5556 0.1829 305.86)`, idéntico al `background-color` del panel.
**Arreglo** — el block pasa `color="var(--color-content-on-solid)"`, el mismo
token que ya usa para la tinta de su copia; medido después, el tinte resuelve a
`#ffffff`.
### A-102 · A-103 · A-104 · A-105 — ARREGLADO · decoración de sección · F5
**Reclamación** — la columna B del plan de calidad pedía acabado de fondo en
`stats-band` (con reveal, que ya estaba), `testimonials` y `feature-split`; y
`site-footer` / `banner` no tenían la capacidad siquiera.
**Mecanismo** — los cuatro primeros montan `<Background><Background.Pattern>` como
HIJA de su `Section`: la sección se vuelve anfitriona por la regla de foundation
(`:where(:has(> [data-background]))`), así que la banda sangra a todo el ancho
detrás de la medida y ningún block gana una caja. `site-footer` y `banner` la
reciben APAGADA: la columna B no pide acabado en ninguno de los dos.
**Evidencia** — medido en Chrome, copia sobre el lienzo de página (`10,10,11`,
tinta `238,238,238`): llano **17,06:1**; con `glow` **13,23:1**; con `grid` /
`dots` **12,37:1**. Los cinco anfitriones computan `position: relative` +
`isolation: isolate` sin que ningún block los posicione.
**Nota de alcance** — `feature-split` resuelve la alternancia en la sección y no
por fila a propósito: una banda por fila obligaría a cada `Row` a poseer el
estado de alternancia, que es coordinación, y un block que coordina deja de ser
un block que no posee nada.
### A-106 — CONFIRMADO · `Button` (canon) · doctrina · MEDIA
**Mecanismo** — `ButtonProps` hereda `ref` de la cadena de soma, así que `bind:ref={x}` **compila y tipa sin una queja**; pero `button.svelte` de eidos no declara `ref` ni lo hace `$bindable`: sólo esparce `...rest`. Un `bind:` sobre una prop que el componente no declara bindable no crea vínculo, así que la variable se queda en `null` para siempre y en silencio.
**Evidencia** — medido al componer `cookie-consent`: con `bind:ref` el foco nunca volvía al disparador; buscando el mismo botón por `id` a través de `uix.dom.getDocument()`, vuelve. `grep -n ref src/uix/eidos/components/button/button.svelte` no devuelve nada.
**Disposición** — es la forma de [A-94]: o se reenvía el `ref` (declararlo `$bindable` y pasarlo) o se quita del tipo. Un tipo no puede prometer una superficie que descarta.
### A-107 — CONFIRMADO · `Switch` (canon) · doctrina · BAJA
**Mecanismo** — el morfo de `switch` declara `provider` (el botón) y `thumb`, nada más. El hijo del componente ES el thumb, así que una etiqueta pasada como hijo se pinta DENTRO de la pista — medido: el texto encima del carril y el thumb sin renderizar. La etiqueta visible tiene que ser hermana, y entonces no es diana de clic.
**Evidencia** — `src/uix/morfo/components/switch.ts` · todos los consumidores del repo repiten el mismo apaño (`web/routes/alpha/+layout@.svelte`, `web/routes/alpha/components/button/+page.svelte`, `cookie-consent.svelte`), unos con `aria-label` duplicando el texto visible y otros con `aria-labelledby`.
### A-108 — CONFIRMADO · `Dialog` (canon) · doctrina · MEDIA
**Mecanismo** — al cerrar, el diálogo devuelve el foco al elemento que lo tenía cuando se abrió. Si ese elemento se desmontó entretanto —lo normal cuando el disparador vive en una superficie que el propio diálogo oculta— el foco cae al `body`. Y aunque el consumidor lo devuelva él mismo, **la trampa sigue armada mientras el contenido se anima al salir**: un foco movido fuera se recupera al instante.
**Evidencia** — trazado en `cookie-consent` parcheando `HTMLElement.prototype.focus`: restauración propia a 1897ms, la del diálogo encima a 1902ms, y el foco acabando en un `switch` que se estaba desmontando.
**Disposición** — la salida que funciona hoy, ya en `cookie-consent.svelte`: `onCloseAutoFocus` con `preventDefault()` **más** devolver el foco un frame después (`dom.raf`). Que haga falta ese baile es el defecto.
### A-109 — CONFIRMADO · `Banner` (canon) + `banner` · doctrina · MEDIA
**Mecanismo** — `banner.svelte` del canon estampa `role="banner"` DESPUÉS de esparcir sus rest props, así que ni el block ni la app pueden quitarlo, y toda página con cabecera acaba con dos landmarks `banner`. El README del block ya lo documentaba («nombrar ambos con `aria-label` es todo lo que puede una app»); la página compuesta lo mide.
**Evidencia** — `/blocks/landing`: `querySelectorAll('header,[role=banner]')` devuelve dos y, sin `aria-label` en la tira, uno queda anónimo en el árbol de accesibilidad. `src/uix/eidos/components/banner/banner.svelte:41`.
### A-110 — CONFIRMADO · `feature-split` · doctrina · MEDIA
**Mecanismo** — es el único block de sección sin parte `.Header`: `feature-grid`, `testimonials`, `faq` y `pricing` la tienen. Sus `Row` emiten `Title` a `level={2}` por defecto, así que una sección de tres filas aporta **tres `h2` hermanos** y la sección misma no tiene nombre. Un app puede componer el encabezado a mano como primer hijo (el root renderiza `{@render children()}` dentro de un `Stack`) y bajar las filas a `level={3}` —lo que hace la página compuesta—, pero eso es reconstruir a mano lo que el resto del tier declara.
**Evidencia** — `src/uix/blocks/feature-split/types.ts` (sin `header`) · `feature-split-title.svelte:6` (`level = 2`) · el outline medido en `/blocks/landing`.

Powered by TurnKey Linux.