La pieza existe desde ayer. La fila pasa a ARREGLADO con sus cifras: solape 0px
entre el aviso y la cabecera, hit-test dentro de la cabecera, documento = 720 =
el viewport. Lo que la cierra no es un número mejor sino haber quitado la
pregunta que tumbó todas las propuestas anteriores («¿y si tenemos banners a
diferentes alturas?»): el aviso entra en flujo como una fila, así que su altura
es la que sea.
⚠️ Sigue siendo cierto lo que la ficha decía del `Affix`: fuera de un shell, un
aviso con `affix="top"` sigue tapando una cabecera pegada. Es el apilamiento
firmado, no un defecto.
A-109 gana disposición escrita (es del canon: el `role="banner"` va después de
los rest props; dos salidas, y se decide en una sesión del canon).
El handoff cambia de puerta: F3 está abierta y `app-shell` hecho. Queda escrito
lo que la sesión NO llegó a ejecutar — la app de referencia en modo attach, que
sigue siendo el único sitio donde el ecosistema entero se demuestra cableado, y
hoy no lo hace ninguno de los siete shells del repo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ -138,7 +138,7 @@ Columna **or.** = de qué sección del documento viejo salió la fila (`C` confi
| 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 |
| 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 |
| 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 |
| 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-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` | ARREGLADO |
| 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 |
| 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 |
@ -368,7 +368,7 @@ Los trece restantes, por filas vivas primero: `pricing` · `faq` · `site-header
### A-95 — CONFIRMADO · `banner` · doctrina · MEDIA
### A-95 — ARREGLADO · `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.
@ -380,6 +380,26 @@ Los trece restantes, por filas vivas primero: `pricing` · `faq` · `site-header
**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).
**CERRADA 2026-08-19 — la pieza existe.** El block `app-shell` (F3.1) se
construyó y con él el solape desaparece por CONSTRUCCIÓN, no por compensación:
bajo `scroll="main"` el shell no fija nada. Medido en
`/blocks/app-shell/preview?scroll=main&banner=true` a 1280×720 con la tira
puesta: el aviso ocupa `[0, 54]`, la cabecera `[54, 85]`, **solape 0px**, y
`document.elementFromPoint` en el centro de la cabecera devuelve LA CABECERA.
El documento mide 720 = el viewport, así que tampoco hay página que se desplace
por debajo del cromo.
Lo que lo resuelve no es un número mejor sino quitar la pregunta que el autor
hizo y que tumbó todas las propuestas anteriores («¿y si tenemos banners a
diferentes alturas?»): el aviso entra EN FLUJO como una fila de la rejilla, así
que su altura es la que sea — una línea, dos, o dos avisos — y la cabecera se
ancla debajo sin que nadie declare nada.
⚠️ Sigue siendo cierto lo que la ficha decía del `Affix`: un aviso con
`affix="top"` FUERA de un shell sigue tapando una cabecera pegada, y eso no es
un defecto sino el apilamiento firmado. El README de `banner` sigue siendo el
sitio donde decirlo.
### A-96 — CONFIRMADO · `banner` · doctrina · BAJA
@ -1979,6 +1999,16 @@ un block que no posee nada.
**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`.
**Disposición (escrita 2026-08-19, con `app-shell` delante)** — Es del CANON y
sigue abierta: mientras `banner.svelte` estampe `role="banner"` después de los
rest props, ni un block ni una app pueden quitarlo. Lo que el shell aporta es
que la página deja de necesitar el apaño para OTRA cosa: sus regiones se nombran
solas, así que el único landmark sin nombre posible es el segundo `banner`. Dos
salidas, y no son excluyentes: (a) canon — estampar el `role` ANTES de los rest
props, que es la clase de precedencia «naming» donde el consumidor gana
(`reference_aria_two_class_precedence_a85`); (b) app — nombrar los dos, que es
lo único disponible hoy. Se decide en una sesión del canon, no del tier.
### 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.
por ROL de landmark; peldaño propio de z (950, el más alto de la escalera
estática, porque un empate lo rompe el orden del DOM y este enlace es el
primer elemento).
- **Cuatro trampas de layout medidas**, todas escritas en el README del block:
`minHeight` es un suelo y no un techo · un grid que sólo declara filas tiene
una columna implícita `auto` que se encoge · `Box` declara
`flex-grow/shrink/basis` y pisa al `flex` de la receta de quien lo envuelve ·
**un ancestro con `overflow` se lleva el `position: sticky` de dentro** (por
eso el shell pone su propia caja en vez de `Sidebar.Inset`).
- **Una avería del ARNÉS, no del block**: los previews del tier llevaban los 8px
de margen por defecto del navegador, así que NINGÚN block se enseñaba a
sangre (`x: 8, w: 1264` a 1280), contra la regla dura del tier — la página
compuesta incluida, cuyo README cita cifras a sangre. Arreglado en
`_lib/reset.css`. ⚠️ **Las medidas de anchura tomadas antes del 2026-08-19
llevan ese sesgo.**
- **Gaps de canon que abrió, anotados en el README del block**: el eje `layout`
del `Sidebar` (provider ≠ fila) para una topbar a todo lo ancho, que pedirá
`docs-shell` · `Sidebar.Inset` debería dejar elegir si es contenedor de
scroll · el `Sidebar` no publica su ancho actual · F21 (los primitivos de
layout no cambian de elemento) obliga a dos declaraciones en línea sobre
`<main>` y `<aside>`.
- **Lo que la fase 0 NO ejecutó y sigue sobre la mesa**: la **app de referencia**
(`web/routes/blocks/app/`, `kind: 'page'`) — la primera raíz de composición en
modo attach del repo (`createActiveApp` → `attachActiveUix` → `<Uix>` →
`ActiveEidos` con sus fuentes → `createActivePrefsDomProjection` →
`setActiveApp`/`setBus`/`setPermsContext`). Hoy **ningún** shell del repo hace
nada de eso: los siete arrancan `createActiveUix` standalone y cada uno
reescribe a mano su `localStorage` y su `Set<listener>` de modo. Era el tramo
F3 del plan de la sesión y no se llegó.
Detrás, por orden: [§Qué queda, por orden](#qué-queda-por-orden) — las filas
CONFIRMADO del ledger (110 filas; **A-95 pasó a ARREGLADO el 2026-08-19**).
## ⚠️ LO PRIMERO: la fuente viva es el LEDGER, no el documento del 2026-08-01
@ -240,33 +287,31 @@ análisis completo).
### a) Bloqueadas por decisión TUYA — no las toco sin tu firma
| Fila | Qué hay que decidir |
| --- | --- |
| **A-67**`faq` | Dos earcons simultáneos al cambiar de pregunta. Puerta ya elegida en la ficha: un nuance `emerge-collapse-swap` (morfo + provider + pack). **Cae en el eje de sema**, así que se ejecuta en una sesión suya, no aquí. |
| **A-47**`cta` (ALTA) | Anillo de foco. Por doctrina es **un eje de configuración**: se endurece con un VALOR en `color.focus.ring`, «never a per-component CSS change» — que es justo lo que hacía el intento revertido en `e468e764b`. |
| **A-09**`stats-band` | Su disposición APAGARÍA la detección live: el contrato exige `value` para detectarla. El defecto real es el anuncio crudo en `metrics-value.svelte:40`, que es canon. |
| **A-67**`faq`| Dos earcons simultáneos al cambiar de pregunta. Puerta ya elegida en la ficha: un nuance `emerge-collapse-swap` (morfo + provider + pack). **Cae en el eje de sema**, así que se ejecuta en una sesión suya, no aquí. |
| **A-47**`cta` (ALTA) | Anillo de foco. Por doctrina es **un eje de configuración**: se endurece con un VALOR en `color.focus.ring`, «never a per-component CSS change» — que es justo lo que hacía el intento revertido en `e468e764b`. |
| **A-09**`stats-band` | Su disposición APAGARÍA la detección live: el contrato exige `value` para detectarla. El defecto real es el anuncio crudo en `metrics-value.svelte:40`, que es canon. |
### b) Bloqueada por una pieza que no existe
### b) ~~Bloqueada por una pieza que no existe~~ — CERRADA 2026-08-19
- **A-95**`banner` — con `affix="top"` la tira tapa la cabecera pegada. No se
arregla en ninguno de los dos blocks: falta la pieza que POSEE las alturas de
página. Mantine lo resuelve en su `AppShell` (`header={{ height }}`); nuestro
equivalente es el block **`app-shell`, F3.1, sin construir**. El plan ordena
ya F3 con `app-shell` PRIMERO por esto (y porque F4.1 lo especializa); la
página compuesta (F2b tramo D) es donde A-95 se re-mide.
- ~~**A-95** `banner`~~**ARREGLADA**: la pieza era `app-shell` (F3.1) y ya
existe. Solape 0px y hit-test dentro de la cabecera, medido. Lo que sigue
siendo cierto: un `affix="top"` FUERA de un shell sigue tapando una cabecera
pegada — es el apilamiento firmado, y el README de `banner` es donde se dice.
### c) Las 5 que dejó la página compuesta (F2b-D, 2026-08-18)
Todas medidas, todas CONFIRMADO, ninguna tocada — salen de componer 14 blocks en
un documento y de construir `cookie-consent`:
| Fila | Qué |
| --- | --- |
| **A-106**`Button` (canon) | Acepta `ref` en su tipo y NUNCA lo reenvía: `bind:ref` tipa bien y no ata nada. **Forma exacta de A-94.** |
| **A-106**`Button` (canon) | Acepta `ref` en su tipo y NUNCA lo reenvía: `bind:ref` tipa bien y no ata nada. **Forma exacta de A-94.**|
| **A-107**`Switch` (canon) | El morfo no tiene parte de etiqueta: la etiqueta visible es hermana y no es diana de clic. Todos los consumidores del repo repiten el mismo apaño. |
| **A-108**`Dialog` (canon) | Devuelve el foco al nodo que lo tenía al abrir; si el disparador se desmontó, acaba en el `body` — y la trampa sigue armada un frame más. |
| **A-109**`Banner` (canon) | DOS landmarks `banner` en toda página con cabecera; el `role` se estampa DESPUÉS de los rest props. |
| **A-110**`feature-split` | Único block de sección sin `.Header`: sus filas emiten `h2` y la sección se queda sin nombre. |
| **A-108**`Dialog` (canon) | Devuelve el foco al nodo que lo tenía al abrir; si el disparador se desmontó, acaba en el `body` — y la trampa sigue armada un frame más. |
| **A-109**`Banner` (canon) | DOS landmarks `banner` en toda página con cabecera; el `role` se estampa DESPUÉS de los rest props. |
| **A-110**`feature-split`| Único block de sección sin `.Header`: sus filas emiten `h2` y la sección se queda sin nombre. |
### d) Las 15 restantes — trabajo normal
@ -715,7 +760,7 @@ navega»).
- `npm run blocks:check` verde con las tres reglas nuevas, 15 blocks / 114