|
|
|
|
@ -0,0 +1,385 @@
|
|
|
|
|
# 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 `<Container size={container}>` sin reenviar `paddingX`, y `...rest` va al `<section>` (`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<BoxProps,'display'>` y el bloque doc de :58-65 promete «Composes through <Box> 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 `<svelte:element this={tag} …>` (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 — `<Link variant="subtle" color={layout === 'background' ? 'var(--color-content-on-solid)' : undefined}>`. Medido en /blocks/hero/preview?layout=background: el `<a data-link>` 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 `<Drawer>` 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 `<header>` and NO heading…» y hero/README.md:28-30 «**Landmark + heading**: one `<section>` 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 `<section {...rest}>` 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 `<section>` 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 `<form data-form>` (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 `<Form>` entero por un `<Text>`; 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>` (`form-provider.svelte.ts:1…
|
|
|
|
|
- Sugerencia del auditor: Mantener el `<Form>` 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` `<Box maxWidth="60ch">` envolviendo el `<Text as="p" …>` de `cta.svelte:73-81`. El canon tiene el eje: `src/uix/eidos/components/text/types.ts:58-59` `measure?: ResponsiveProp<TextMeasure>` («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 `<style>` nunca es "visible"), despues de correr las entradas de viewport (0 `[data-animation-pending]`), y sobre atributos que YA aplico la runtime del morfo (`data-state` + `aria-pressed` presentes en ambos items). No es F15–F22 ni es el contrato B-1: el defecto no es que el block carezca de morfo, es que su propio control escribe un valo…
|
|
|
|
|
- Sugerencia del auditor: Sustituir el `Box maxWidth="60ch"` por `measure="narrow"` en el `Text` (o justificar el 60ch como `literal:` si la medida elegida es intencionalmente intermedia).
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `stats-band` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 `value` y `children` a la vez por dos caminos distintos.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/stats-band/stats-band-value.svelte`
|
|
|
|
|
- Evidencia: `stats-band-value.svelte:20` `<Metrics.Value value={count} {...rest}>` con `<CountUp to={count}>` como children (`:22`). En el canon, `metrics-value.svelte` dispara `ctx?.notifyUpdate(el, String(next))` al cambiar `value`, y `metrics.svelte:57-63` lo pasa como `message` de `signal-notify-update` (que `metrics/context.svelte.ts:11-13` describe como «flash + sound/haptic + live-region announce»). Lo renderizado, en cambio, lo formatea `CountUp` vía `uix.format.numbers` (`count-up.svelte:63-86`). O sea: con `count=12500` se vería «12.500» y se anunciaría «12500». La demo publicita `live` como parte del API de `.Stat` (`web/routes/blocks/stats-band/+page.svelte:131-135`). NO lo he reproducido en…
|
|
|
|
|
- Veredicto: El hallazgo SE SOSTIENE, y además la causa que dio el auditor es la correcta (lo he medido, no deducido). Mecanismo verificado en el código: - `src/uix/morfo/components/drawer.ts:180-184` declara en la parte `Trigger` un `aria-label` con `value: v.translationRef('#?components.drawer.trigger|Open drawer')` **sin `condition`**. Al no ser literal, el compilador lo mete en `dynamicAttrs` (`src/uix/morfo/compile.ts:418-425`). - `src/uix/soma/components/drawer/drawer-provider.svelte.ts:415-423` regist…
|
|
|
|
|
- Sugerencia del auditor: Decidir el camino único: o no pasar `value` cuando hay `count` (live no anuncia cifras contadas) o formatear el mensaje con `uix.format` antes de notificar. Verificar con `live` + un `count` que cambie.
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `site-header` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 sigue `open` y el bloqueo de scroll del body sigue puesto: la página queda inmovilizada y no hay NADA en pantalla que lo explique.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/site-header/site-header.svelte`
|
|
|
|
|
- Evidencia: site-header.svelte:56-66 — `<Box display={{ base: 'contents', [breakpoint]: 'none' }}>` envuelve el `<Drawer>` completo (Overlay + Content, sin `Drawer.Portal`, que sí existe: eidos/components/drawer/index.ts:58). Medido en http://localhost:5180/blocks/site-header/preview?breakpoint=md — abrir el cajón a 500px, redimensionar a 1200px y disparar `resize`: {drawerState:'open', drawerVisible:false, overlayVisible:false, triggerVisible:false, hiddenByAncestorStyle:'--box-display: none;', bodyStyle:'overflow: hidden;', pageScrollable:true}. Solo `Escape` recupera (verificado: state pasa a 'closed' y el style del body se vacía) — un usuario táctil que gira la tableta se queda sin salida visible. E…
|
|
|
|
|
- Veredicto: CONFIRMADO, y la causa raíz es de canon, no del block. `Card` declara sus props como `Omit<BoxProps,'display'>` (src/uix/eidos/components/card/types.ts:66) — TypeScript acepta `height` — pero `card.svelte` NO compone `Box`: su destructuring (líneas 24-39) no recoge `height`, así que cae en `...rest` y se derrama tal cual en el `<svelte:element this={tag}>` (línea 131 sobre la 108). Como `interactive=false` el tag es `<div>`, y `height` no es atributo presentacional de un `<div>`: es INERTE. Box …
|
|
|
|
|
- Sugerencia del auditor: El block YA posee el `breakpoint` que provoca el corte: cerrar `mobileOpen` cuando la variante ancha toma el relevo, leyendo el estado reactivo del propio framework (`uix.dom.isAtLeast(breakpoint)` / `currentBreakpoint`, no un `matchMedia` propio — sigue siendo B-6). Alternativa mínima: envolver el Drawer en `Drawer.Portal` para que no cuelgue del Box que se apaga, aunque eso deja el cajón abierto…
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `site-header` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 desde el control vivo aunque el block lo acepte.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `web/routes/blocks/site-header/SiteHeaderSite.svelte`
|
|
|
|
|
- Evidencia: SiteHeaderSite.svelte:25-26 `type ContainerSize = 'sm'|'md'|'lg'|'xl'|'full'` y `type Breakpoint = 'sm'|'md'|'lg'`. Canon: eidos/components/container/types.ts:4 incluye `'xxl'` (token real: generated/base.css:143 `--container-width-xxl`), y libs/dom/responsive.ts:1 declara seis valores ('base'|'sm'|'md'|'lg'|'xl'|'xxl'). Mismo calco en site-header/+page.svelte:24-25, site-header/preview/+page.svelte:13-14, hero/HeroSite.svelte:30-33 y hero/preview/+page.svelte:13-19 (este último recalca también `BackdropPattern`). El block hace lo correcto (site-header/types.ts:3-4 importa los tipos del canon); la demo no.
|
|
|
|
|
- Veredicto: Se sostiene, y con una consecuencia MEDIBLE que el auditor no llegó a nombrar. Los tres datos que aportó son literales y correctos: hero.svelte:46 deriva `onDark`, se consume SOLO en :81 (Display) y :92 (Text); types.ts:43-64 declara `eyebrow`/`actions`/`media`/`children` como `Snippet` sin parámetros y el block no hace `setContext` (a diferencia de contact/pricing/team/content-section, que sí tienen context.ts); HeroSite.svelte:103 re-deriva `layout === 'background'` a mano; y README.md:36 dice…
|
|
|
|
|
- Sugerencia del auditor: Importar `ContainerSize`, `Breakpoint`, `SectionSize`, `HeroLayout` y `BackdropPattern` desde su origen en las cuatro demos. Así un valor nuevo del canon rompe a nivel de tipo en vez de quedarse sin control vivo en silencio.
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `feature-grid` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 fuentes mienten.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/feature-grid/types.ts`
|
|
|
|
|
- Evidencia: types.ts:40 «Wraps a **soft** `Surface` chip». web/routes/blocks/feature-grid/+page.svelte:134 «el chip (`Surface` **soft**)». Frente a feature-grid-item-icon.svelte:12 `let { color = 'primary', variant = 'solid', … }` y feature-grid/README.md:17 «`Surface` (solid, accent) … solid so it reads in light mode».
|
|
|
|
|
- Veredicto: Se sostiene, con tres precisiones. CONFIRMADO: (a) el block emite encabezados propios con nivel FIJO 3 y sin escotilla por su parte — `faq-item.svelte:17` usa `<Accordion.Header>` sin `level`, `faq-item.svelte:10` destructura sin `...rest`, y `types.ts:24-33` cierra `FaqItemProps` a 4 props (`value`,`disabled`,`question`,`children`); el nivel viene del default `level = 3` de `soma/components/accordion/components/accordion-header.svelte:16`, y el canon SÍ lo expone (eidos `accordion-header.svelte…
|
|
|
|
|
- Sugerencia del auditor: Corregir el JSDoc de `FeatureGridItemIconProps` y el DocRow de la demo a `solid` (que es la decisión razonada del README), mencionando el `variant="soft"` como opción.
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `testimonials` — percepcion
|
|
|
|
|
|
|
|
|
|
**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 miden distinto.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/testimonials/testimonials-item.svelte`
|
|
|
|
|
- Evidencia: Medido en Chrome (preview de testimonials, barrido de anchos): a 940px la celda 2 mide 196 px y su `[data-card]` 169 px; a 820px y 760px la celda 4 mide 196 px y su card 169 px (27 px de hueco muerto bajo la tarjeta). El atributo aparece crudo en el DOM: `card.getAttribute('height')` = "100%" sobre un `<div>`. Falsa: `README.md:16` («fills the row height») y `README.md:27-28` («`.Author` pins to the card's bottom (`margin-block-start: auto`) so a row of uneven quotes still lines its faces up»), más la demo `web/routes/blocks/testimonials/+page.svelte:42-43` («tarjetas de igual alto»). A 1280px no se nota porque las cinco citas caen en el mismo número de líneas — la demo enmascara el fallo ju…
|
|
|
|
|
- Veredicto: Se sostiene: es deriva real entre el typedoc público y el código, no un falso positivo. (1) `src/uix/blocks/faq/types.ts:17` promete «Wraps a `Box` (measure-cap, centered)»; `faq-header.svelte:12` es `<Box maxWidth="48rem" width="100%">` y `Box` NO emite margen auto (box.css:242-251 solo escribe `margin-*` desde `--box-margin-*`, que aquí van sin poner → computed `margin-left/right: 0px`, medido); `faq.svelte:23` apila con `align="stretch"` (computed `align-items: stretch`, medido). Con `align-i…
|
|
|
|
|
- Sugerencia del auditor: Misma brecha de canon que en `pricing` (`Card` no consume `BoxProps` pese a declararlas). Registrar en Gaps y/o corregir el README hasta que el canon lo sostenga; añadir a la demo una cita larga y otra corta para que el desalineado se vea al ancho por defecto.
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `feature-split · pricing · testimonials` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 del tier.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/pricing/README.md`
|
|
|
|
|
- Evidencia: B-8 (`docs/architecture/blocks.md:126`): «Correct landmarks: sectioning element + `aria-label`/`aria-labelledby` where landmarks repeat; heading hierarchy coherent and **documented in the block README** (which level it emits, how to adjust)». La plantilla lo pide con ese mismo nombre en `src/uix/blocks/README.md:80-81`. `grep -n -i "heading hierarchy|Landmark +" src/uix/blocks/*/README.md` devuelve SOLO `hero/README.md:28` y `site-header/README.md:21`. Los tres auditados emiten un `<section>` sin nombre accesible (`feature-split.svelte:20`, `pricing.svelte:36`, `testimonials.svelte:19` — todos `<section {...rest}>`) mientras los hermanos con la misma forma sí lo nombran (`cta.svelte:102`, `h…
|
|
|
|
|
- Veredicto: SE SOSTIENE, y es mas amplio de lo que dijo el auditor. Comprobado en las cuatro patas (tipo, compilado, runtime de Svelte y DOM vivo), y descartadas las causas de falso positivo: no es medicion pre-hidratacion (espere `style#uix-blocks-display` con `state:'attached'` — el `<style>` esta oculto, `visible` da timeout falso), no es pestana de fondo (0 `[data-animation-pending]` tras hacer scroll), no es transform de una entrada sin correr (no mido rects, leo `getComputedStyle` y el `style` inline)…
|
|
|
|
|
- Sugerencia del auditor: Añadir a cada README el párrafo «Landmark + heading hierarchy» que ya escribe `hero`: qué elemento de seccionado emite, con qué nombre (o por qué va sin nombre — un `<section>` anónimo NO se expone como region, y decirlo es la mitad del valor), qué nivel de encabezado pone el block (`.Title` h2, `.PlanName` h3) y cómo lo ajusta la app. Decisión separada a elevar: `pricing` y `testimonials` renderi…
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `contact` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 nunca lo encuentra para saber que está bloqueado) y la razón es un `<span>` pelado, sin `role="status"`/`aria-live` y sin `aria-describedby` desde el botón. B-7 dice que el registro que bloquea la acción escribe la frase «so it cannot go silent» — en el canal de AT sí es muda.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `G:/dev/svelte/vicen/src/uix/blocks/contact/contact-reason.svelte`
|
|
|
|
|
- Evidencia: contact-reason.svelte:24-26 renderiza `<Text>` sin rol ni live; contact-submit.svelte:21 pasa `disabled` nativo. MEDIDO en /blocks/contact: botón `disabled=true`, `aria-describedby=null`; el único `[aria-live]` de la página es un div vacío ajeno. El cambio de estado incomplete→unverified cambió el texto del span («Completa todos los campos…» → «Resuelve la verificación de arriba.») sin ninguna región viva que lo anuncie. Doctrina: docs/architecture/blocks.md:125 (B-7 enmendado).
|
|
|
|
|
- Veredicto: El hallazgo SE SOSTIENE, y medido en navegador es peor de lo que el auditor dijo. Intenté refutarlo por las cuatro vías habituales de falso positivo y ninguna aplica: (a) medí tras la puerta de hidratación real (`style#uix-blocks-display`), con scroll completo y esperando a que no quedara ningún `[data-animation-pending]`, así que la entrada `scale-fade` ya había corrido; (b) los valores son `getComputedStyle().color`, que no lo tocan los transforms de la entrada; (c) no es ninguno de los huecos…
|
|
|
|
|
- Sugerencia del auditor: `Contact.Reason` con `role="status"` (o `aria-live="polite"`) y un `id` que `Contact.Submit` referencie por `aria-describedby`; si el canon `Form.Submit` no admite `aria-describedby`, ese es el hueco a promover.
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `contact` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 eso — lee `form.errors` y renderiza un `Field.ErrorText` por cada uno de los tres campos.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `G:/dev/svelte/vicen/src/uix/blocks/contact/README.md`
|
|
|
|
|
- Evidencia: README.md:50-53 («**It does not validate and it does not render fields' errors.**») vs contact-fields.svelte:37-40 (`errorOf()` leyendo `form?.errors?.[field]?.[0]`) y :53-55, :63-65, :76-78 (`<Field.ErrorText>{errorOf(...)}</Field.ErrorText>`).
|
|
|
|
|
- Veredicto: El hallazgo SE SOSTIENE, y lo he probado por A/B sobre el elemento real, no por lectura de CSS. Cadena verificada de punta a punta: 1. `link.svelte:40,55` — un `color` no canónico entra por `resolveComponentColor()` (`eidos/lib/component-color.ts:57`) y solo produce `data-color-custom` + el semilla inline `--color-custom: …`. NUNCA escribe `color` inline. 2. `generated/base.css:4769` — `[data-link][data-color-custom]` deriva `--_link-palette-text: var(--palette-text, …)`. La derivación SÍ corre.…
|
|
|
|
|
- Sugerencia del auditor: Corregir la frase: el block SÍ pinta el error del campo (traducido, que es el motivo de F20). Y si se resuelve el hallazgo alta, esa rama por fin dejará de ser inalcanzable.
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `content-section` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 código y toda la doctrina del block dicen `measure` — precisamente la decisión de cabecera («una figura que se ensancha sin que se lo pidas es una sorpresa»).**
|
|
|
|
|
|
|
|
|
|
- Fichero: `G:/dev/svelte/vicen/web/routes/blocks/content-section/+page.svelte`
|
|
|
|
|
- Evidencia: web/routes/blocks/content-section/+page.svelte:193 «<Code>width</Code> (def. <Code>wide</Code>)» vs src/uix/blocks/content-section/content-section-media.svelte:26 `let { width = 'measure', … }` y README.md:86-90 + types.ts:88 (`@default 'measure'`). La propia demo se contradice: +page.svelte:22-24 comenta que el control arranca en el default del block y arranca en `measure`.
|
|
|
|
|
- Veredicto: CONFIRMADO. El mecanismo es exactamente el descrito: `[data-banner]` es `display:flex` (banner.css:20), el `Container` del block lleva `margin-left:auto` + `margin-right:auto` (container.svelte:26-30, `align='center'` por defecto, estampados como `marginLeft`/`marginRight` en 57-58) y `Banner.Close` añade `margin-inline-start:auto` (banner.css:150-152). Tres márgenes automáticos en el eje principal → el algoritmo flex reparte el espacio libre en TRES partes iguales en vez de dos, y la columna de…
|
|
|
|
|
- Sugerencia del auditor: Cambiar «def. wide» por «def. measure» en el DocRow de `ContentSection.Media`.
|
|
|
|
|
|
|
|
|
|
### [MEDIA] `team` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 legado y no se añaden más). Es el único fichero de los tres blocks que lo tiene.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `G:/dev/svelte/vicen/src/uix/blocks/team/team-member-links.svelte`
|
|
|
|
|
- Evidencia: team-member-links.svelte:15-17: «// Pegada al fondo de la tarjeta: con biografías de distinto largo, las filas / de enlaces quedarían a alturas distintas y la retícula se leería descuadrada. / Sin biografías las tarjetas ya miden lo mismo y esto no cambia nada.» (CLAUDE.md §Code Style: «Comments in English… don't add new ones»).
|
|
|
|
|
- Veredicto: Reproducido, y no cae en ninguna de las causas de falso positivo. Medí DESPUÉS de la puerta de hidratación (`style#uix-blocks-display`), tras recorrer la página entera y esperar a que no quedara ningún `[data-animation-pending]`, y con `getComputedStyle` (no `getBoundingClientRect`, así que ningún transform de entrada contamina). Mismos números en `/blocks/site-footer` y en `/blocks/site-footer/preview?columns=4`, en claro y en oscuro. No es ninguno de los huecos congelados F15–F22 (verifiqué §6…
|
|
|
|
|
- Sugerencia del auditor: Traducirlo — y de paso reescribirlo, porque tal como está describe un efecto que no ocurre (ver el hallazgo alta de este mismo fichero).
|
|
|
|
|
|
|
|
|
|
### [BAJA] `faq` — percepcion
|
|
|
|
|
|
|
|
|
|
**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 pegada al inicio en cuanto el `container` es más ancho que 48rem.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/faq/types.ts`
|
|
|
|
|
- Evidencia: `faq/types.ts:17` dice «Wraps a `Box` (measure-cap, centered)». `faq-header.svelte:12` es `<Box {maxWidth} width="100%">` (Box no emite margen auto salvo `marginX`) y `faq.svelte:23` es `<Stack gap={8} align="stretch">`. Los DOS bloques con el MISMO comentario sí centran: `pricing.svelte:39` y `testimonials.svelte:22` usan `align="center"` — faq copió la frase sin la alineación. Medido en Chrome (localhost:5180/blocks/faq/preview, viewport 1280): con el default (`container='md'`) coinciden (container 768@x251, header 736@x267); emulando `container="lg"` (--box-max-width:1024px) el contenido va 139→1147 (centro 643) y la cabecera queda 768@x139 (centro 523) = 120px descentrada respecto al aco…
|
|
|
|
|
- Veredicto: Se sostiene. Las cuatro fuentes dicen lo que el auditor afirma, y el navegador confirma que lo que se pinta es SOLID. types.ts:40 declara «Wraps a soft `Surface` chip» siguiendo el mismo patron con que sus lineas hermanas declaran defaults («Wraps a `Stack`. `align` defaults to `start`», «Wraps a muted `Text`»), asi que el adjetivo ES el default declarado, no prosa. La demo repite «el chip (`Surface` soft)» en +page.svelte:134. El codigo (feature-grid-item-icon.svelte:12) pone `variant = 'solid'…
|
|
|
|
|
- Sugerencia del auditor: O centrar de verdad (`marginX="auto"` en el Box de `faq-header.svelte`, o `align="center"` en el Stack raíz como pricing/testimonials) o borrar «centered» del typedoc. Es una línea, pero hay que decidir cuál de las dos es la intención.
|
|
|
|
|
|
|
|
|
|
### [BAJA] `faq` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 landmark ni la jerarquía de encabezados.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/faq/README.md`
|
|
|
|
|
- Evidencia: `faq-item.svelte:17` renderiza `<Accordion.Header>` desnudo; `FaqItemProps` (`faq/types.ts:24-33`) tiene 4 props y ninguna es `level`, y `.Item` no hace spread de `...rest` (`faq-item.svelte:10`), así que no hay escotilla. El nivel sale del default del canon (`src/uix/soma/components/accordion/components/accordion-header.svelte:16` → `level = 3`). Medido en Chrome (/blocks/faq/preview): 5 × `DIV[role=heading][aria-level=3]`. B-8 exige «heading hierarchy coherent and documented in the block README (which level it emits, how to adjust)» y el README de faq no tiene línea de landmark/heading — su mapa de composición (línea 13) solo dice «the app's section `Heading` + `Text`». El resto del tier s…
|
|
|
|
|
- Veredicto: Se sostiene. El tipo publico documenta un default que el componente nunca usa: types.ts:26 dice «Wraps a `Card` (the quote card). `variant` defaults to `soft`.» mientras testimonials-item.svelte:13 destructura `variant = 'outline'` y lo reenvia con `<Card {variant} …>` (variant sale del rest, asi que `...rest` no puede reponerlo). No es regresion: `git show b59332132` (commit que crea el block) ya trae 'outline' en el .svelte y 'soft' en el tipo — el origen de la deriva es que la frase copia el …
|
|
|
|
|
- Sugerencia del auditor: Añadir la línea «Landmark + heading» al README (section sin nombre accesible + `role=heading aria-level=3` por pregunta) y exponer `level` en `FaqItemProps` reenviándolo a `Accordion.Header`, para una página que meta el FAQ bajo un h3.
|
|
|
|
|
|
|
|
|
|
### [BAJA] `stats-band` — doctrina
|
|
|
|
|
|
|
|
|
|
**El typedoc de `countOptions` anuncia una opción `style` que `CountUp` NO acepta — pasarla es error de tipos.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/stats-band/types.ts`
|
|
|
|
|
- Evidencia: `stats-band/types.ts:38-40`: «Everything `CountUp` takes (`from`, `duration`, `notation`, `style`…)». Pero `src/uix/eidos/components/count-up/types.ts:3` es `Omit<HTMLAttributes<HTMLElement>, 'children' | 'style'>` — `style` está EXCLUIDA a propósito; la prop de formato se llama `formatStyle` (`count-up/types.ts:39`, `count-up.svelte:31`).
|
|
|
|
|
- Veredicto: Se sostiene la parte observable, no la causal. HECHOS VERIFICADOS: (1) el calco existe — SiteHeaderSite.svelte:25-26 declara `ContainerSize = 'sm'|'md'|'lg'|'xl'|'full'` y `Breakpoint = 'sm'|'md'|'lg'`, contra canon container/types.ts:4 (6 valores, con 'xxl') y libs/dom/responsive.ts:1 (6 valores); mismo calco en site-header/+page.svelte:24-25, site-header/preview/+page.svelte:13-14, hero/HeroSite.svelte:30-31 y hero/preview/+page.svelte:13-14; el block SI importa del canon (blocks/site-header/t…
|
|
|
|
|
- Sugerencia del auditor: Cambiar `style` por `formatStyle` en el comentario (que además es la que el ejemplo quiere: contar hacia una moneda o un porcentaje).
|
|
|
|
|
|
|
|
|
|
### [BAJA] `stats-band` — doctrina
|
|
|
|
|
|
|
|
|
|
**El README no documenta landmark ni jerarquía de encabezados (B-8); el dato solo vive en la pestaña A11y de la demo.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/stats-band/README.md`
|
|
|
|
|
- Evidencia: `grep -ci 'heading|landmark' src/uix/blocks/stats-band/README.md` = 0 (el único README del tier con 0; hero=4, team=4, banner=4, site-footer=6). El block emite `<section>` desnudo sin nombre accesible (`stats-band.svelte:26`) y ningún encabezado. B-8 (`docs/architecture/blocks.md:126`) pide que la jerarquía esté documentada EN EL README del block. La frase existe, pero en la demo: `web/routes/blocks/stats-band/+page.svelte:154-157` «Sin encabezados: una banda son datos…».
|
|
|
|
|
- Veredicto: El hallazgo se sostiene. `src/uix/blocks/pricing/index.ts:12` muestra `<Pricing.Switch annualLabel="Anual (−20%)" />` mientras que `src/uix/blocks/pricing/types.ts:34-41` declara `monthlyLabel: string`, `annualLabel: string` y `'aria-label': string` las tres SIN `?`, y `src/uix/blocks/pricing/pricing-switch.svelte:15` las desestructura SIN defaults (con un comentario, lineas 11-14, que declara la ausencia deliberada bajo B-7). Intente refutarlo por tres vias y las tres fallan: (1) no es una elis…
|
|
|
|
|
- Sugerencia del auditor: Subir esa misma frase al README (una línea bajo el mapa de composición): «section sin nombre accesible, sin encabezados; si la página necesita título, lo pone alrededor».
|
|
|
|
|
|
|
|
|
|
### [BAJA] `pricing` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 valor que el usuario no eligió, sin decir nada.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/pricing/pricing-switch.svelte`
|
|
|
|
|
- Evidencia: Medido en Chrome (localhost:5180/blocks/pricing/preview, 1280x900), pulsando dos veces «Anual (−20 %)»: before {Mensual=on, precios 0/29/99 €} → click1 {Anual=on, 0/23/79 €} → click2 sobre el MISMO «Anual» {Mensual=on, 0/29/99 €}. Cadena exacta: `pricing-switch.svelte:20-26` compone `<ToggleGroup selectionMode="single">` SIN `deselectable={false}`, y el default de soma es `deselectable: true` (`src/uix/soma/components/toggle-group/types.ts:17-23`: «In `single` mode, whether re-pressing the active item deselects it … @default true»). La re-pulsación entrega `onValueChange([])` y la línea 25 lo traga: `ctx?.setPeriod((v[0] ?? 'monthly') as BillingPeriod)`. `grep -rn deselectable src/uix/blocks…
|
|
|
|
|
- Veredicto: SE SOSTIENE LA PREMISA, SE REFUTA LA CONCLUSIÓN. Cierto y reproducible: `container="full"` NO produce sangre. `SIZE_TO_WIDTH.full` es `var(--container-width-full)` = `none` (G:/dev/svelte/vicen/src/uix/eidos/generated/base.css:144), es decir solo quita el TOPE de anchura; el padding inline es independiente y siempre se aplica — `container.svelte:49` `paddingX ?? 'var(--container-padding-inline)'` = `--space-4` = 16px (base.css:145 y :87). `cta.svelte:104` monta `<Container size={container}>` sin…
|
|
|
|
|
- Sugerencia del auditor: Un periodo de facturación es radio-like, no clearable: pasar `deselectable={false}` en `pricing-switch.svelte:20` (o el canónico `enabledSelections={[1,1]}`) y quitar el `?? 'monthly'` de la línea 25, que hoy convierte silenciosamente «selección vacía» en «mensual». Doctrina §Coordination: el estado que la sección posee no puede cambiar por un camino que la pantalla no explica.
|
|
|
|
|
|
|
|
|
|
### [BAJA] `pricing` — percepcion
|
|
|
|
|
|
|
|
|
|
**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 la demo afirman exactamente lo contrario.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/pricing/pricing-plan.svelte`
|
|
|
|
|
- Evidencia: Medido en Chrome (preview de pricing, 1280x900, con el gate de Motion neutralizado para que la visibilidad no confunda la lectura): wrapper de rejilla 505/505/505 px, pero `[data-card]` 388/505/461 px, y los bottoms de los tres `[data-button]` en y=667 / 784 / 741 → **117 px de desfase**. El DOM lo delata: `<div data-card="" data-variant="outline" … height="100%">` — `height` quedó como atributo crudo sobre un `<div>` (`getComputedStyle(card).height` = 387.875px). Origen en canon: `src/uix/eidos/components/card/types.ts:61,66` declara `CardProps = Omit<BoxProps,'display'>` y dice «Composes through `<Box>` so consumers can apply layout primitives (gridColumn, margin, gap, etc.)», pero `card.s…
|
|
|
|
|
- Veredicto: SE SOSTIENE EL NÚCLEO MECÁNICO, PERO LA MITAD DE LA EVIDENCIA ES FALSA Y EL ENCUADRE «doctrina» ESTÁ INFLADO. 1) VERDADERO y probado: la autogeneración del block es duplicación inerte. El canon ya la resuelve por la MISMA ruta de composición: `src/uix/soma/components/accordion/components/accordion-item.svelte:16` (`value = useId('accordion-item-value')`), y el wrapper eidos la deja pasar tal cual — `src/uix/eidos/components/accordion/accordion-item.svelte:5,8` desestructura `{ children, ...rest …
|
|
|
|
|
- Sugerencia del auditor: Es una brecha del canon a promover, no a parchear dentro del block (`Card` debe aceptar de verdad su superficie `BoxProps` o dejar de declararla). Mientras tanto, o el block deja de prometer alineación en README+demo, o el estirado se consigue con un componente de layout que sí lo cumpla. Nota: `pricing-plan-action.svelte:14` (`marginTop="auto"`) sí funciona — empuja el botón al borde de SU tarjet…
|
|
|
|
|
|
|
|
|
|
### [BAJA] `pricing` — doctrina
|
|
|
|
|
|
|
|
|
|
**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.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `web/routes/blocks/pricing/+page.svelte`
|
|
|
|
|
- Evidencia: `web/routes/blocks/pricing/+page.svelte:53`: «`featured` le da acento y elevación, y un `badge` encima». Contra `src/uix/blocks/pricing/pricing-plan.svelte:8-11`: «The featured tier is NOT elevated: `Card` exposes no elevation prop, and a block paints nothing of its own … Registered as a gap in the README instead of faked with an inline `box-shadow`». Y contra `src/uix/blocks/pricing/README.md:74`: «**Elevation on the featured plan** — canon candidate … hasta entonces `featured` es color only».
|
|
|
|
|
- Veredicto: Se sostiene la MITAD de encabezados; refuto la de landmark. CIERTO: el README de stats-band no dice ni una palabra sobre jerarquía de encabezados, y es el único del tier con 0 coincidencias de 'heading|landmark'. La obligación no es solo B-8 (docs/architecture/blocks.md:126) — la plantilla del tier la exige literalmente en src/uix/blocks/README.md:78-79 ("Landmark + heading hierarchy: which sectioning element / heading level the block emits and how the app adjusts it", justo tras el Composition …
|
|
|
|
|
- Sugerencia del auditor: Quitar «y elevación» de la línea 53 de la demo. La única superficie donde el visitante lee la doctrina del block no puede contradecir la decisión firmada.
|
|
|
|
|
|
|
|
|
|
### [BAJA] `pricing` — doctrina
|
|
|
|
|
|
|
|
|
|
**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 la enmienda del 2026-07-31 sustituyó.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/pricing/pricing-switch.svelte`
|
|
|
|
|
- Evidencia: `pricing-switch.svelte:11-15` comenta «NO defaults for the three strings: a block ships zero words of its own (B-7)» y `types.ts:29-41` hace `monthlyLabel: string; annualLabel: string; 'aria-label': string` REQUERIDAS. Pero `docs/architecture/blocks.md:125` (B-7) dice: «**Amended 2026-07-31**: a block DOES own the words of the states it coordinates (and only those), as idlangrefs with an English fallback resolved through the translator (`#?blocks.<block>.<key>|…`)», y :141 sanciona el traductor como el único servicio permitido «to resolve the block's own state words». El periodo de facturación ES el estado que `Pricing` coordina (`context.ts:12-17`) y `monthlyLabel`/`annualLabel` son literal…
|
|
|
|
|
- Veredicto: El ejemplo del front door del block anuncia una prop que la API no tiene y que el propio block documenta como CORTADA a propósito. `src/uix/blocks/cta/index.ts:11` reza `// <Cta layout="justified" variant="soft">…</Cta>`, pero `CtaProps` (`src/uix/blocks/cta/types.ts:15-61`) no declara `variant` y su base es `Omit<HTMLAttributes<HTMLElement>, 'children' | 'title'>` — comprobado en `node_modules/svelte/elements.d.ts:751-858`, ese interface NO tiene índice `[key: string]: any` (solo `[key: \`data-…
|
|
|
|
|
- Sugerencia del auditor: Volver las tres cadenas OPCIONALES con idlangref + fallback inglés (`'#?blocks.pricing.period.monthly|Monthly'`, `…annual|Annual`, `…switch.label|Billing period`) resueltos con `ActiveEidos.require().langs.t`, como hace `contact`; la app las sigue sustituyendo pasando las suyas. Y actualizar el comentario, que hoy cita la versión derogada de B-7.
|
|
|
|
|
|
|
|
|
|
### [BAJA] `testimonials` — doctrina
|
|
|
|
|
|
|
|
|
|
**El tipo público documenta un default de `variant` que el componente no usa.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/testimonials/types.ts`
|
|
|
|
|
- Evidencia: `types.ts:26`: «Wraps a `Card` (the quote card). `variant` defaults to **`soft`**.» Contra `testimonials-item.svelte:13`: `variant = 'outline',`. El README acierta (`README.md:16`: «wrapping a `Card` (**outline**)»), así que la deriva está solo en el tipo — el sitio donde la lee el editor.
|
|
|
|
|
- Veredicto: Se sostiene. El typedoc de `countOptions` en src/uix/blocks/stats-band/types.ts:38 lista `style` como una de las props que «CountUp takes», pero CountUpProps la excluye explicitamente (src/uix/eidos/components/count-up/types.ts:3 = Omit<HTMLAttributes<HTMLElement>, 'children' | 'style'>) y la prop de formato real se llama `formatStyle` (count-up/types.ts:40, destructurada en count-up.svelte:31 y usada como `style: formatStyle` hacia Intl.NumberFormatOptions en formatValue). Pasar `style` dentro …
|
|
|
|
|
- Sugerencia del auditor: Corregir a `outline` en `types.ts:26`.
|
|
|
|
|
|
|
|
|
|
### [BAJA] `testimonials` — doctrina
|
|
|
|
|
|
|
|
|
|
**`.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.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `src/uix/blocks/testimonials/testimonials-items.svelte`
|
|
|
|
|
- Evidencia: `testimonials-items.svelte:11-19`: `<AutoGrid {...rest} minChildWidth={…} {columns} {gap} align="stretch" width="100%" data-stagger>`. Como `TestimonialsItemsProps = AutoGridProps` (`types.ts:24`) y `AutoGridProps = Omit<GridProps,…>` → `GridProps` → `BoxProps`, escribir `<Testimonials.Items align="start" width="60ch">` tipa perfecto y no hace absolutamente nada. El block hermano hace lo contrario en el mismo directorio: `testimonials-item.svelte:24` pone `{...rest}` AL FINAL, así que ahí el consumidor sí manda. `pricing-plans.svelte` evita el problema declarando un tipo cerrado sin rest (`types.ts:44-52`).
|
|
|
|
|
- Veredicto: El ejemplo de la cabecera de `src/uix/blocks/newsletter/index.ts:5` enseña `<Newsletter {form} {schema} onValidSubmit={subscribe}>`, y `NewsletterProps` NO tiene ese prop: comprobado con `tsc`, no razonado. `NewsletterProps = Omit<HTMLAttributes<HTMLElement>,'children'|'title'> & {…}` y `HTMLAttributes` de Svelte solo trae dos índices — `[key: \`data-${string}\`]`y`[key: symbol]`(node_modules/svelte/elements.d.ts:853-857) — no hay comodín`on\*`, así que el nombre no entra por ningún hueco del…
|
|
|
|
|
- Sugerencia del auditor: O cerrar el tipo a las props que el block realmente honra (como hace `PricingPlansProps`), o mover `{...rest}` al final y aceptar que `align`/`width`/`data-stagger` son sobreescribibles. Prometer y descartar es lo único que no vale.
|
|
|
|
|
|
|
|
|
|
### [BAJA] `pricing · testimonials` — percepcion
|
|
|
|
|
|
|
|
|
|
**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 que solo puede verse ahí.**
|
|
|
|
|
|
|
|
|
|
- Fichero: `web/routes/blocks/pricing/+page.svelte`
|
|
|
|
|
- Evidencia: `web/routes/blocks/pricing/+page.svelte:14` (`const previewSrc = '/blocks/pricing/preview'`) y `web/routes/blocks/testimonials/+page.svelte:12` pasan la ruta pelada, sin query. El `+layout@.svelte` del preview lee los ejes SOLO de la URL (`page.url.searchParams.get('mode'|'dir'|'lang')`, con default `light`/`ltr`/`es`), y `_lib/BlockDemo.svelte:146` usa `src={previewSrc}` tal cual sin añadir nada. Todas las demás demos pasan al menos `dir` (banner:22, hero:28-29, contact:20, newsletter:22, site-footer:20, site-header:29, feature-split:20). Contradice `src/uix/blocks/README.md:60-61`: «The section's axes (colour mode, language, direction, density) live in the shell … so every demo inherits th…
|
|
|
|
|
- Veredicto: Se sostiene el NUCLEO, pero la solucion que propone el auditor es medible y falsa, y hay que corregirla o el "arreglo" cambia el diseno. LO QUE HOLDS: (1) `60ch` no es escalon de la escala (`--measure-narrow:54ch · normal:66ch · wide:78ch`, base.css:323-325); (2) el canon declara que ESTE es el vocabulario del tier: `src/uix/eidos/lib/types.ts:127-128` dice literalmente que «the blocks tier sizes a reading column with TextMeasure»; (3) un block hermano YA lo obedece: `content-section/grid.ts:31-…
|
|
|
|
|
- Sugerencia del auditor: Propagar `mode`/`lang`/`dir` del shell al `previewSrc` — mejor en `_lib/BlockDemo.svelte`, que es donde el problema es sistémico, para que ningún demo tenga que acordarse. Sin eso, la verificación visual pendiente de «light/dark × RTL a 375/768» no es ejecutable desde la galería.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Sin verificar (50) — NO son hallazgos hasta refutarlos
|
|
|
|
|
|
|
|
|
|
### Severidad alta
|
|
|
|
|
|
|
|
|
|
- **`newsletter`** (doctrina) — 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 a 19 ms: la composición `Form` > `Field` duplica el mismo verbo. · `src/uix/blocks/newsletter/newsletter.svelte:105`
|
|
|
|
|
- **`newsletter`** (doctrina) — `signal-warn-invalid` está declarado `persistence:'untilFix'` pero NUNCA se limpia al arreglar: corregido el correo, el form sigue estampado en `risk` indefinidamente mientras el texto de error y `aria-invalid` ya dicen que está resuelto. · `src/uix/soma/components/form/form-provider.svelte.ts:129`
|
|
|
|
|
- **`site-header`** (percepcion) — 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 suena con la ganancia BASE de la familia commit (0.30) en vez de con el tuning `form.commit.subtle` (0.03) elegido para navegacion de alta frecuencia — 10x mas fuerte de lo disenado. · `src/uix/sema/components/navigation-menu.ts`
|
|
|
|
|
- **`site-header`** (percepcion) — 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 sobre la barra dispara dos confirmaciones afirmativas de algo que el usuario no ha fijado. · `src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.ts`
|
|
|
|
|
- **`site-header`** (percepcion) — 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 timbre indistinguible (misma fundamental y mismo intervalo). · `web/routes/blocks/site-header/SiteHeaderSite.svelte`
|
|
|
|
|
- **`site-header`** (percepcion) — MUDOS: 4 de los 7 elementos interactivos visibles del header no emiten NADA, y son precisamente los que navegan — el brand «Acme», los `NavigationMenu.Link` «Precios» y «Docs», y el `Link` «Entrar». En el drawer, los 5 enlaces de navegacion tambien. La navegacion real es silenciosa mientras el despliegue del menu canta. · `web/routes/blocks/site-header/SiteHeaderSite.svelte`
|
|
|
|
|
- **`site-header`** (percepcion) — 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 de pulsacion del boton hamburguesa se trunca 40x por debajo de su hold canonico. · `src/uix/blocks/site-header/site-header.svelte`
|
|
|
|
|
- **`banner`** (doctrina) — 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 propio block eligió — invisible en la forma que su README dice haber adoptado. · `src/uix/blocks/banner/banner.svelte`
|
|
|
|
|
- **`banner`** (percepcion) — 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 refuta: el camino de color custom OSCURECE la semilla un 30 %, y el hover ignora el color del link por completo. · `web/routes/blocks/banner/+page.svelte`
|
|
|
|
|
- **`newsletter`** (doctrina) — 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 silencioso, y que su propio tipo rechaza. · `src/uix/blocks/newsletter/index.ts`
|
|
|
|
|
- **`contact`** (percepcion) — 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 ms despues, cuando el envio ya ha resuelto. · `src/uix/blocks/contact/contact-submit.svelte`
|
|
|
|
|
- **`contact`** (percepcion) — 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`
|
|
|
|
|
- **`contact`** (percepcion) — 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 llegan a la tecnologia asistiva. · `src/uix/blocks/contact/contact-submit.svelte`
|
|
|
|
|
- **`contact`** (percepcion) — `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 que nada la anuncie. · `src/uix/blocks/contact/contact-reason.svelte`
|
|
|
|
|
- **`contact`** (doctrina) — 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 error del propio block es inalcanzable por construccion. · `src/uix/blocks/contact/contact-fields.svelte`
|
|
|
|
|
- **`pricing`** (percepcion) — Un solo clic sobre el periodo YA activo VACIA el segmented control: la seccion queda sin ningun periodo marcado mientras los precios siguen siendo los mensuales — el unico control que coordina el block miente sobre el estado de su seccion. · `src/uix/blocks/pricing/pricing-switch.svelte`
|
|
|
|
|
- **`cta`** (percepcion) — La accion secundaria del cluster («Hablar con ventas») es MUDA: no emite ningun evento sema, y en reposo es tipograficamente identica al parrafo de la descripcion — ni se siente ni se ve como accion. · `web/routes/blocks/cta/CtaSite.svelte:72`
|
|
|
|
|
- **`cta`** (percepcion) — El anillo de foco es practicamente invisible sobre el panel solido: los DOS unicos elementos interactivos del block quedan sin indicador de foco por teclado. · `src/uix/blocks/cta/cta.svelte:111`
|
|
|
|
|
|
|
|
|
|
### Severidad media
|
|
|
|
|
|
|
|
|
|
- **`banner`** (percepcion) — 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 ese estampado además cae sobre un nodo ya fuera del documento, así que su canal visual no puede pintarse. · `src/uix/blocks/banner/banner.svelte:59`
|
|
|
|
|
- **`banner`** (percepcion) — 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`
|
|
|
|
|
- **`site-footer`** (percepcion) — 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 su gesto principal, mientras sus tres iconos decorativos sí suenan. · `src/uix/blocks/site-footer/site-footer.svelte:77`
|
|
|
|
|
- **`newsletter`** (doctrina) — La acción principal del block no tiene `contact` propio: pulsar «Suscribirme» no estampa NADA sobre el control — el único estampado es el `commit` del `<form>` —, así que se incumple la regla de composición «contact precede al resultado cuando hay acción directa». · `src/uix/morfo/components/form.ts:90`
|
|
|
|
|
- **`site-header`** (percepcion) — 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, mientras Escape y overlay si emiten `emerge.close`. · `src/uix/blocks/site-header/site-header.svelte`
|
|
|
|
|
- **`hero`** (percepcion) — 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 `contact-activate` y el enlace hermano es MUDO. · `web/routes/blocks/hero/HeroSite.svelte`
|
|
|
|
|
- **`site-header`** (doctrina) — El morfo de `NavigationMenu` carga el intent evaluable en el MARCO: declara un unico evento, `commit-select` + `affirm`, para lo que solo despliega un panel, y no declara ninguno para su parte `Link`, que es el acto evaluable de verdad (navegar). · `src/uix/morfo/components/navigation-menu.ts`
|
|
|
|
|
- **`banner`** (doctrina) — «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 margen automático y el flex reparte el hueco libre en tres, descentrando la columna del mensaje. · `src/uix/blocks/banner/banner.svelte`
|
|
|
|
|
- **`banner`** (doctrina) — 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 catálogo: para `banner`, cuya función ES la tira de borde a borde sobre la cabecera, eso cambia lo que el block hace. · `web/routes/blocks/_lib/BlockDemo.svelte`
|
|
|
|
|
- **`newsletter`** (percepcion) — 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 plena, invirtiendo la jerarquía. · `src/uix/blocks/newsletter/newsletter.svelte`
|
|
|
|
|
- **`site-footer`** (doctrina) — La demo construye tintas a mano en app-land (`color="var(--color-content-*)"`) y son INERTES: con `variant="subtle"` el recipe del Link fuerza `color: inherit` con más especificidad, así que todos los enlaces del pie salen a tinta plena y los legales acaban más oscuros que el copyright que tienen al lado. · `web/routes/blocks/site-footer/SiteFooterSite.svelte`
|
|
|
|
|
- **`site-footer`** (percepcion) — 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`
|
|
|
|
|
- **`site-footer`** (percepcion) — «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 pistas vacías igualmente, así que con 2 grupos las columnas se apelotonan al inicio y casi dos tercios de la región quedan muertos. · `src/uix/blocks/site-footer/site-footer.svelte`
|
|
|
|
|
- **`contact`** (percepcion) — 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`
|
|
|
|
|
- **`team`** (percepcion) — 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`
|
|
|
|
|
- **`contact`** (percepcion) — 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`
|
|
|
|
|
- **`pricing`** (percepcion) — Ese mismo clic muerto emite `commit-toggle` + intent `neutral`: el sistema anuncia que algo quedo fijado cuando la seccion no cambio de estado (los precios no se mueven). · `src/uix/blocks/pricing/pricing-switch.svelte`
|
|
|
|
|
- **`pricing`** (percepcion) — 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 `contact-activate` del Button y la microcompresion del press (`press-squeeze`) no llega a arrancar nunca. · `web/routes/blocks/pricing/PricingSite.svelte`
|
|
|
|
|
- **`pricing`** (percepcion) — El plan destacado se grada `fulfill` en lo visual pero su consecuencia suena `affirm`, exactamente igual que los otros dos planes: la lectura unificada se parte en el gesto mas importante de la seccion. · `web/routes/blocks/pricing/PricingSite.svelte`
|
|
|
|
|
- **`faq`** (percepcion) — Un solo clic que cambia de pregunta dispara DOS earcons emerge simultaneos y sin atenuar (collapse descendente + expand ascendente): un gesto, dos sonidos superpuestos de peso casi igual. · `src/uix/blocks/faq/faq-list.svelte:14`
|
|
|
|
|
- **`stats-band`** (percepcion) — La coreografia de la banda esta desincronizada: la ENTRADA escalona, pero los contadores arrancan todos en el mismo frame, asi que una cifra ya va por su 21 % cuando se hace visible. · `src/uix/blocks/stats-band/stats-band-value.svelte:20`
|
|
|
|
|
- **`stats-band`** (percepcion) — 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 asentarse y la pequena 3,6 s — la banda esta en movimiento ocho segundos. · `src/uix/eidos/components/count-up/count-up.svelte:169`
|
|
|
|
|
|
|
|
|
|
### Severidad baja
|
|
|
|
|
|
|
|
|
|
- **`feature-grid`** (percepcion) — 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`
|
|
|
|
|
- **`site-footer`** (doctrina) — 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 incoherente en la superficie donde B-8/B-9 piden que sea coherente. · `web/routes/blocks/_lib/BlockDemo.svelte`
|
|
|
|
|
- **`contact`** (percepcion) — 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`
|
|
|
|
|
- **`content-section`** (percepcion) — 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`
|
|
|
|
|
- **`feature-split`** (percepcion) — Los 3 unicos elementos interactivos del block emiten su contact correctamente, pero no llevan a ningun sitio: los href apuntan a fragmentos que no existen en el documento, asi que el arco perceptivo muere en «te he sentido» y no ocurre nada. · `web/routes/blocks/feature-split/FeatureSplitSite.svelte`
|
|
|
|
|
- **`pricing`** (percepcion) — 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 «ya estaba elegido». · `web/routes/blocks/pricing/PricingSite.svelte`
|
|
|
|
|
- **`testimonials`** (percepcion) — 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`
|
|
|
|
|
- **`stats-band`** (percepcion) — 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 es la entrada (Motion viewport + data-stagger + CountUp); no se midio ni un solo data-event originado en el block. · `src/uix/blocks/stats-band/stats-band.svelte`
|
|
|
|
|
- **`stats-band`** (doctrina) — 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 titulares; B-8 pide landmark correcto y jerarquia documentada, y el README no dice nada de ninguna de las dos. · `src/uix/blocks/stats-band/stats-band.svelte:26`
|
|
|
|
|
|
|
|
|
|
### Severidad sospecha
|
|
|
|
|
|
|
|
|
|
- **`site-footer`** (percepcion) — 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 igual que elegir un ítem cualquiera de una lista: `commit-select + affirm`, sin ningún `shift`. · `web/routes/blocks/site-footer/SiteFooterSite.svelte:164`
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Refutados (11) — NO volver a reportarlos
|
|
|
|
|
|
|
|
|
|
- **`faq`** — El block reimplementa la autogeneración del `value` que el canon ya hace, y README+index la venden como «la única conveniencia» de `.Item`.
|
|
|
|
|
- Por qué no se sostiene: Las dos premisas del hallazgo fallan al medirlas. (1) «El README no declara landmark» es FALSO. La fila `root` del Composition map lo declara: `src/uix/blocks/feature-grid/README.md:13` → «root | bare `<section>` + `Section` + `Container` + `Stack`». Lo que no hay es una frase sobre el NOMBRE accesi…
|
|
|
|
|
- **`cta`** — El ejemplo del `index.ts` anuncia `variant="soft"`, una prop que se CORTÓ a propósito: copiarlo es un error de tipos.
|
|
|
|
|
- Por qué no se sostiene: Los hechos citados son exactos (las tres props son requeridas y el comentario invoca B-7 en términos absolutos), pero la conclusión doctrinal es falsa: la enmienda del 2026-07-31 NO sustituyó la lectura que aplica a `pricing`, y el proyecto ya resolvió este caso concreto por escrito y con posteriori…
|
|
|
|
|
- **`cta`** — La demo redeclara en app-land la unión que el block EXPORTA (`CtaLayout`), duplicando vocabulario.
|
|
|
|
|
- Por qué no se sostiene: La afirmación, tal y como está redactada («ninguno de los tres README declara el landmark ni la jerarquía de encabezados»), es falsa en sus dos mitades, y la evidencia de apoyo contiene un error de hecho y una insinuación de código que se mide como no-problema. 1) EL LANDMARK SÍ ESTÁ DECLARADO en lo…
|
|
|
|
|
- **`hero`** — 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 de uso — el patrón exacto que la doctrina de coordinación señala como generador de huecos.
|
|
|
|
|
- Por qué no se sostiene: REFUTADO como hallazgo de `pricing`/`testimonials`. Dos de las tres piezas del hallazgo se caen: 1. El diferenciador es falso EN EFECTO. El auditor apoya la acusación en «todas las demás demos pasan al menos `dir`». Ese `dir` no es el eje de la sección: es un `$state` local de cada demo, con default…
|
|
|
|
|
- **`feature-grid`** — `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 en tres sitios más, y la demo lo repite tres veces.
|
|
|
|
|
- Por qué no se sostiene: Los HECHOS mecánicos son ciertos, pero la acusación (doctrina: «aplicado a medias» + «el README lo niega») no se sostiene. (1) La cita del README es un error de alcance: `src/uix/blocks/feature-grid/README.md:34-40` es la sección «Coordination», que remite explícitamente a `docs/architecture/blocks.…
|
|
|
|
|
- **`site-header`** — 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.
|
|
|
|
|
- Por qué no se sostiene: No se sostiene como defecto de doctrina, por cuatro razones medidas. (1) La propia doctrina exime a `faq` POR SU NOMBRE: `docs/architecture/blocks.md:82-86` lo lista entre los blocks de layout que «own nothing» y dice que inventarles estado «would be the opposite mistake»; la regla «fires where a se…
|
|
|
|
|
- **`pricing`** — El ejemplo de uso de la superficie pública del block no compila: omite dos props obligatorias.
|
|
|
|
|
- Por qué no se sostiene: El HECHO citado es cierto (las tres líneas existen literalmente), pero NO es un defecto del block `cta` ni sostiene su consecuencia. (1) La duplicación está SOLO en app-land: el block es fuente única — `src/uix/blocks/cta/types.ts:10` declara `CtaLayout`, `cta.svelte:35` consume `CtaProps` importado…
|
|
|
|
|
- **`contact`** — 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 inválido no puede ocurrir porque el botón está deshabilitado salvo en `ready`. Resultado medido: con nombre «A» y correo «roto» no hay ni un `ErrorText`, ni un `aria-invalid`, y la única frase en pantalla es «Completa todos los campos para enviar.» — falsa, porque los tres campos están llenos.
|
|
|
|
|
- Por qué no se sostiene: El fenomeno tecnico existe (el anuncio lleva `String(value)` y la pantalla el formato de locale), pero el hallazgo NO se sostiene como defecto del block ni como asunto de doctrina B en `stats-band-value.svelte`: 1. ATRIBUCION ERRONEA. Es comportamiento del canon `Metrics`, reproducible en su propia …
|
|
|
|
|
- **`team`** — `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í que no hay espacio libre que el margen automático pueda comerse. Con biografías de distinto largo —el caso exacto para el que el README dice que existe la regla— las filas de enlaces quedan a alturas distintas y la retícula se lee descuadrada.
|
|
|
|
|
- Por qué no se sostiene: Los hechos del DOM se confirman, pero el hallazgo NO se sostiene como defecto de `site-footer` ni como incumplimiento de B-8/B-9. 1) MAL ATRIBUIDO. La causa está en el shell compartido `web/routes/blocks/_lib/BlockDemo.svelte` y el propio auditor admite que afecta a las 15 demos. No es una propiedad…
|
|
|
|
|
- **`contact`** — `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 ya no puede volver a enviar nunca. El usuario puede reescribir el mensaje entero y el botón se queda en «Enviado», deshabilitado, con «Gracias. Te respondemos por correo.» debajo. La máquina no tiene arista de vuelta ante un cambio de valores.
|
|
|
|
|
- Por qué no se sostiene: La MEDICIÓN es exacta y reproducible (auto-fill en `auto-grid.svelte:26`, 5 pistas, 443px libres con 2 grupos), pero las tres afirmaciones que la convierten en defecto son falsas: 1. **«se apelotonan al inicio» es falso.** Medido en las cuatro cuentas: la pista mide _lo mismo_ siempre — 123,59px con…
|
|
|
|
|
- **`contact`** — Vocabulario paralelo: `ContactVerification` re-declara palabra por palabra el `ProofOfHumanStatus` del canon, y lo hace señalando a ese mismo componente como la fuente. Si el canon renombra o añade un estado, el block no rompe a nivel de tipos — el valor nuevo cae en silencio en el cajón `!== 'verified'`.
|
|
|
|
|
- Por qué no se sostiene: La tesis del hallazgo —«el raíl del catálogo mete el block en un marco y eso cambia lo que `banner` hace»— NO se sostiene. Tres razones medidas: (1) B-9 prohíbe una cosa concreta y estrecha: «never inside a padded frame or a scroll box» (docs/architecture/blocks.md:127). Medí la cadena de ancestros …
|