# AUDIT — tier blocks (2026-08-01) > Congelado desde el workflow `wf_31fd1de3-ed6` (50 agentes). El journal del workflow es de > SESIÓN y desaparece: esta es la única copia. No re-reportar lo refutado. ## Método Dos dimensiones en paralelo sobre los 15 blocks — **doctrina** (contrato B + regla de coordinación, por lectura de fuente/README/demo) y **percepción** (sema en vivo en navegador, interacción por interacción) — y después un verificador ESCÉPTICO por hallazgo, con el encargo de refutarlo y la lista de falsos positivos conocidos del repo (medir antes de hidratar, pestaña en segundo plano, transforms de entradas sin correr, confundir un hueco congelado F15–F22). La dimensión CRUZADA (los 15 en una página compuesta) **no se llegó a ejecutar**. | | | | ------------------ | -------- | | Hallazgos en bruto | 90 | | Únicos | 90 | | Verificados | 40 | | **Confirmados** | **29** | | Refutados | 11 (28%) | | **Sin verificar** | **50** | ⚠️ El tope de verificación (40) lo puso el script, no el trabajo: **50 hallazgos quedaron sin refutar, 18 de ellos marcados ALTA**. No son menos graves: están sin comprobar, y la tasa de falsos positivos medida es del 28%. --- ## Confirmados (29) ### [ALTA] `cta` — doctrina **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 escotilla, así que el panel nunca llega al borde.** - Fichero: `src/uix/blocks/cta/README.md` - Evidencia: `cta/README.md:81` (y la pestaña Gaps de la demo, `web/routes/blocks/cta/+page.svelte:191-193`) prometen sangre con `container="full"`. Pero `src/uix/eidos/components/container/container.svelte:49` hace `paddingX ?? 'var(--container-padding-inline)'` = `var(--space-4)` = 16px (`src/uix/eidos/generated/base.css:145` y `:87`); `full` solo quita el cap (`--container-width-full: none`). `cta.svelte:104` monta `` sin reenviar `paddingX`, y `...rest` va al `
` (`cta.svelte:102`), no al Container. Medido en Chrome (/blocks/cta/preview, viewport 1280, body 1254): el `[data-surface]` pasa de 992@x139 a 1222@x24 al emular `full` — sigue metido 16px por lado (y con `… - Veredicto: CONFIRMADO, y el mecanismo es exactamente el descrito. `src/uix/eidos/components/card/types.ts:66` declara `CardProps = Omit` y el bloque doc de :58-65 promete «Composes through so consumers can apply layout primitives», pero `card.svelte` NO importa Box: destructura solo variant/size/color/rounded/shape/interactive/selected/disabled/noEmerge/motion/onSelect/style/children y vuelca el resto con `{...rest}` sobre `` (card.svelte:108-134). Por… - Sugerencia del auditor: O reenviar `paddingX` (o un `bleed`) al `Container`, o reescribir la disposición del gap: `container="full"` da ancho completo del contenido, NO sangre — la sangre real hoy no existe. ### [ALTA] `hero` — percepcion **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 no hace absolutamente nada.** - Fichero: `web/routes/blocks/hero/HeroSite.svelte` - Evidencia: HeroSite.svelte:99-106 — ``. Medido en /blocks/hero/preview?layout=background: el `` computa `color: oklch(0.2435 0 0)` (rgb 32,32,32) y un clon sondeado SIN el override computa EXACTAMENTE lo mismo — el valor crudo entra por `--color-custom` (lib/component-color.ts:57) y `variant="subtle"` hereda la tinta de página igual. Fondo compuesto bajo el enlace = gradiente `oklch(0.459 0.151 305.86)` + scrim `rgb(28 25 23 / 0.42)` a `opacity .45` → rgb(94,53,128). Contraste enlace/fondo **1.78:1**; el título, que el block SÍ pasa a on-solid, mide 9.14:1 en el mismo punto. - Veredicto: CONFIRMADO, y el punto débil de la evidencia original (su propio `pageScrollable:true`) queda resuelto: `window.scrollTo()` sigue moviendo el documento porque `overflow:hidden` no bloquea el scroll programático, pero SÍ bloquea el del usuario. Medido con rueda real y con swipe táctil real: la página no se mueve. Mecanismo verificado en el código: `site-header.svelte:56-66` envuelve el `` COMPLETO (Trigger + Overlay + Content, sin `Drawer.Portal` — que existe: `eidos/components/drawer/ind… - Sugerencia del auditor: Que el block ponga la tinta on-solid en el CONTENEDOR de la copia bajo `background` (no elemento a elemento en Display/Text), de forma que lo que el app suelte en `actions` / `eyebrow` / `children` la herede; y retirar de la demo el `color="var(--color-content-on-solid)"`, que es un token a mano y además inerte. ### [ALTA] `feature-grid` — doctrina **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í lo hacen.** - Fichero: `src/uix/blocks/feature-grid/README.md` - Evidencia: feature-grid/README.md no contiene ninguna línea de landmark (la «Composition map» acaba en la fila `.ItemText`). Contraste: site-header/README.md:21-25 «**Landmark + headings**: emits a single `
` and NO heading…» y hero/README.md:28-30 «**Landmark + heading**: one `
` named by its title…». La plantilla lo exige en src/uix/blocks/README.md:78-79 y B-8 en docs/architecture/blocks.md:126. El hecho existe pero solo en la demo (feature-grid/+page.svelte:173-177: «Sección sin nombre por ahora»), mientras el block emite `
` sin nombre accesible en feature-grid.svelte:25. - Veredicto: SE SOSTIENE (con dos correcciones al diagnostico del auditor, ninguna lo refuta). 1) El override ES inerte, y por una razon MAS FUERTE que la que dio el auditor. La causa no es que el valor crudo entre por `--color-custom`: es `src/uix/eidos/components/link/link.css:34-37`, `[data-link][data-variant='subtle'],[data-link][data-variant='plain'] { color: inherit }`, que gana por especificidad (0,2,0) a `[data-link] { color: var(--_link-palette-text) }` (0,1,0) de link.css:21. Sondas en vivo: el clo… - Sugerencia del auditor: Añadir el párrafo al README: emite un `
` sin nombre (por tanto no es landmark expuesto), la cabecera del app es `h2`, `.ItemTitle` es `h3` ajustable por su prop `level`, y `aria-label`/`aria-labelledby` pasan por `...rest`. ### [ALTA] `contact` — doctrina **`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», porque el canon `Form.Submit` impone su propio `aria-label` y gana en el merge. En `sending` y `sent` el nombre accesible contradice al visible, y en reposo «Enviar» ni siquiera contiene la etiqueta visible «Enviar mensaje» (falla WCAG 2.5.3 Label in Name).** - Fichero: `G:/dev/svelte/vicen/src/uix/blocks/contact/state.ts` - Evidencia: MEDIDO en /blocks/contact/preview: texto «Enviar mensaje» / `aria-label="Enviar"`; tras enviar, texto «Enviado» y `aria-label="Enviar"`. Causa: src/uix/soma/components/form/form-provider.svelte.ts:198 `'aria-label': this.resolvedLabel` + src/uix/soma/props/props.ts:131 (`b !== undefined ? b : a`, y `state.props` va de segundo). - Veredicto: CONFIRMADO, y no es ninguno de los falsos positivos habituales. Cadena causal cerrada en el código: `src/uix/blocks/banner/banner.svelte:59` pasa `variant="ghost"` y NINGÚN `color`; `src/uix/eidos/components/banner/banner-close.svelte:30-37` reenvía ese `color` undefined al `IconButton`, que cae al default canónico → el nodo sale con `data-color="primary"` (leído del DOM real, no inferido). En `button.css:208-214` el slice `ghost` fija `--_button-fg: var(--button-palette-text)`, o sea la tinta P… - Sugerencia del auditor: El `aria-label` del canon debería ceder ante el contenido cuando hay children (o ante un `aria-label` explícito del consumidor). Como el block no puede pisarlo, es un hilo de canon a registrar; mientras tanto la afirmación de state.ts:86 debería acotarse a «a la vista». ### [ALTA] `contact` — doctrina **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 public arts»). No es un ciclo ni una importación entre blocks —`blocks:check` pasa limpio— pero el contrato escrito y el código no coinciden.** - Fichero: `G:/dev/svelte/vicen/src/uix/blocks/contact/contact.svelte` - Evidencia: contact.svelte:28 `import { createForm } from '$libs/forms';` (también context.ts:2 y types.ts:3) vs docs/architecture/blocks.md:95-97. Nota: soma consume `$libs/forms` igual (src/uix/soma/components/form/form-provider.svelte.ts) y no hay puerta de canon que exporte `createForm`, así que probablemente lo que falta es enmendar la doctrina, no el código. - Veredicto: Intenté refutarlo por las cuatro vías habituales y ninguna aplica: (1) medí DESPUÉS de la puerta de hidratación real (`style#uix-blocks-display`), y además `--_link-palette-text` es CSS puro — no depende de runtime alguno; (2) la pestaña estaba activa y `document.querySelectorAll('[data-animation-pending]').length === 0`, `opacity: 1` en el link y en sus 7 ancestros (el block declara explícitamente que NO trae motion de entrada); (3) no hay `getBoundingClientRect` en juego, son colores computado… - Sugerencia del auditor: Añadir `$libs` a la frontera dura 1 (o nombrar explícitamente `$libs/forms` como puerta sancionada), o exportar `createForm` desde el canon `Form` y consumirlo por ahí. ### [ALTA] `site-footer` — percepcion **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 `commit-submit + fulfill` no ocurre en absoluto.** - Fichero: `web/routes/blocks/site-footer/SiteFooterSite.svelte:105` - Evidencia: Medido en /blocks/site-footer con dos instrumentos independientes. (1) Contador WebAudio parcheado sobre `AudioContext.prototype.createOscillator/createBufferSource`: escribir `vicen@example.com` y pulsar «Suscribirme» → **0 nodos de audio**; el MISMO gesto en /blocks/newsletter → 2 osciladores (1 earcon). (2) MutationObserver ligado AL NODO `
` (sobrevive al desmontaje): traza completa = `{dt:0 before-click, connected:true}` → `{dt:46 DETACHED, connected:false}` y NINGUNA mutación `data-event` jamás, ni siquiera ya desprendido. Causa: `SiteFooterSite.svelte:105` `{#if subscribed}` sustituye el `` entero por un ``; el `commit-submit` del morfo de Form es `sequence:… - Veredicto: CONFIRMADO, y en el caso peor es más grave de lo reportado. La cadena es toda del block: `contact.svelte:61` elige `validationBehaviour:'progressive'`, que en `form-core.svelte.ts:337-344` + `:410-411` solo escribe errores tras un submit fallido; y `contact-submit.svelte:21` + `state.ts:59,65-67` solo habilitan el botón cuando `schema.validateSync(values).ok` ya es cierto (`contact.svelte:74-78`). El único disparador de submit del sistema es el `onsubmit` del `` (`form-provider.svelte.ts:1… - Sugerencia del auditor: Mantener el `` montado y añadir la confirmación como hermana —exactamente lo que hace `NewsletterSite.svelte` con su `Callout`—, o si el swap es deliberado, que el `onValidSubmit` emita antes de cambiar el estado. Vale la pena además que la doctrina lo escriba: un `sequence:'post'` no sobrevive al desmontaje de su propio provider. ### [MEDIA] `faq` — doctrina **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 que diga por qué y sin que el block posea ese estado.** - Fichero: `src/uix/blocks/faq/types.ts` - Evidencia: `faq/types.ts:27-28` declara `/** Disable this question. @default false */ disabled?: boolean;` y `faq-item.svelte:16` lo reenvía a `Accordion.Item`. El README §Coordination (`faq/README.md:30-33`) solo dice «`value` is a bindable SEAM… A seam is not ownership», sin mencionar `disabled`. `.Item` no hace spread de `...rest`, o sea que las dos props expuestas son una ELECCIÓN del block, no un passthrough genérico. Es literalmente el patrón que el brief marca como grave (una interacción bloqueada sin frase), aunque aquí quien decide bloquear es la app. - Veredicto: Se sostiene, y es mas amplio de lo que reporto el auditor. Medido en navegador: la Card featured (Pro) NO tiene elevacion de ningun tipo — difiere de las otras SOLO en el color de borde. El unico eje de distincion es cromatico, exactamente como declaran el comentario de implementacion (pricing-plan.svelte:7-11) y la brecha del README (README.md:74). Ademas de la demo, la MISMA afirmacion falsa esta en la superficie publica de la API: types.ts:55 documenta `featured` como «(accent border, elevati… - Sugerencia del auditor: O nombrar `disabled` en §Coordination diciendo por qué una pregunta bloqueada no necesita frase (la app pone el motivo en el texto de la pregunta), o retirarlo. Que la sección enumere una costura y el código exponga dos es la deriva. ### [MEDIA] `cta` — doctrina **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 en el propio `Text` que envuelve.** - Fichero: `src/uix/blocks/cta/cta.svelte` - Evidencia: `cta.svelte:69` `` envolviendo el `` de `cta.svelte:73-81`. El canon tiene el eje: `src/uix/eidos/components/text/types.ts:58-59` `measure?: ResponsiveProp` («needs a block `as`, e.g. `p`» — el bloque YA pone `as="p"`), con escala tokenizada `--measure-narrow: 54ch · --measure-normal: 66ch · --measure-wide: 78ch` (`src/uix/eidos/generated/base.css:323-325`). `60ch` no es ningún escalón. Las propias demos del tier usan el eje canónico: `web/routes/blocks/cta/CtaSite.svelte:36` y `web/routes/blocks/stats-band/StatsBandSite.svelte:51` pasan `measure="wide"`. - Veredicto: CONFIRMADO. No es ninguno de los falsos positivos listados: medí despues de la puerta de hidratacion (`style#uix-blocks-display`, con `state:'attached'` — el `