# CONTINUE — F2 blocks de sitio (handoff, act. 2026-07-30) **Estado: F1 CERRADA (8/8) · F2 11 de 15.** Hechos: site-header · hero · feature-grid · **feature-split** · pricing · testimonials · faq · stats-band · cta · newsletter · site-footer. **Quedan 4**: banner (§F2.11) · team (§F2.12) · contact (§F2.13) · content-section (§F2.14). (El plan fijó 14; `feature-split` se añadió como block hermano con scope-approval, así que el tier tiene 15 y el denominador honesto es 15.) Todo commiteado y pusheado a `gita/alpha-0.1-sec-dom` (último: `eb13fff40`). Dónde vive cada cosa: - **Plan y bitácora por block**: `docs/process/PLAN-blocks.md` (fichas §F2.x + registro cronológico al final, con el detalle de cada cierre). - **Contrato del tier**: `docs/architecture/blocks.md` (B-1..B-11, D-BLK). - **Suelo de paridad**: `docs/process/RESEARCH-blocks-references.md` (el dossier). - **Deuda de canon congelada**: `docs/process/PLAN-blocks-quality.md` §6. - **Decisiones por block**: el README de cada uno (`src/uix/blocks/{kebab}/`). --- ## Lo siguiente: F2.11 `banner` Ficha en `PLAN-blocks.md` §F2.11. Es la **categoría más shipeada del dossier** (13 Tailwind Plus · 16 Untitled · 5 Flowbite), así que la paridad es alta y el listón también. Fase 0 obligatoria, en este orden: 1. **Leer el README del componente `Banner`** (`src/uix/eidos/components/banner/`) y su morfo. La ficha dice «dismiss lo posee Banner si ya lo trae»: si el descarte, la persistencia o el `role` ya son suyos, **el block no los reinventa** — los compone. Si el block acaba necesitando comportamiento nuevo, se promociona al canon ANTES (regla de admisión del tier). 2. Mirar el equivalente en ≥2 catálogos del dossier y anotar en el README del block qué se adopta y qué se descarta. 3. Decidir la forma con la regla del tier (abajo). Un banner tiene una acción y un cierre: probablemente **slots**, no compound. 4. Pareja de posición con `site-header`: va arriba de la página, así que la demo tiene que enseñarlo **encima del header**, no en un recuadro. Después: team → contact → content-section. --- ## La regla de forma del tier (no la re-decidas) Compound **solo** si las partes: - **SE REPITEN** — el app mapea sobre N (`feature-grid.Item`, `testimonials.Item`, `pricing.Plan`, `faq.Item`, `stats-band.Stat`, `site-footer.Column`), o - **COORDINAN** — se hablan por contexto (`pricing.Switch` ↔ `PlanPrice`). Si no hacen ninguna de las dos: **slots de snippet** en la raíz (`hero`, `cta`, `newsletter`, y la marca / alta / social / legal / extra de `site-footer`). El plan original dibujaba varias de esas como partes; la desviación está registrada en cada README. --- ## Los 8 hallazgos de canon que dejó el tier (congelados) Restricción del usuario en vigor: **no tocar componentes ni librerías fuera de `src/uix/blocks/` y `web/routes/blocks/`**. Están todos medidos en navegador y listados en `PLAN-blocks-quality.md` §6 (F15–F22) — más los de motion (A1, A2, A4, C7, C8, C10, D12, E14) de la pasada de calidad. | # | Qué | | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | F15 | `Surface variant='soft'` **no acota un panel**: su track queda a 0.002 de luminancia del fondo de página en claro, y `Surface` no tiene borde. `Card outline` sí acota pero no acepta `gradient` → «panel sosegado con borde» no tiene primitivo. | | F16 | La ranura `contrast` de la paleta es **blanca en todo escalón sólido**, así que un lienzo de luminancia media deja el cuerpo bajo AA: `primary` 5.18 · `indigo` 5.21 · `plum` 4.75 pasan; `neutral` 3.32 · `secondary`/`slate` 3.30 · `teal` 3.07 fallan en claro. | | F17 | **`Text align` es inerte por defecto**: renderiza un `span` y `text-align` no hace nada sobre caja inline. Hay que pedir `as="p"`. | | F18 | **La fundación de eidos no trae reset de modelo de caja y lo asume del app.** Bajo `content-box`, `[data-field-control]` (`inline-size:100%` + padding) mide 30px más que su contenedor. Resuelto en app-land: `web/routes/blocks/_lib/reset.css`. | | F19 | **`onValidSubmit`/`onInvalidSubmit` son no-op silenciosos** si se pasa un `form` ya construido: el componente solo los reenvía al `createForm` que hace él mismo. El handler va SIEMPRE en `createForm`. | | F20 | Los mensajes de SIUM son **idlangref** (`#?sium.errors.email\|…`): resolver con `uix.langs.t(issue.message, issue.params)`. La demo de docs del `Form` los parte a mano y por eso siempre salen en inglés. | | F21 | **Los primitivos de layout no pueden cambiar de elemento**: `Text`/`Heading` aceptan `as`, pero `Box` —y `Stack`/`Flex`/`Grid`/`Group`/`Wrap`/`Container`/`Section`— es un `
` fijo. Una columna de enlaces no puede ser `ul`/`li`. | | F22 | **Un `Select` controlado muestra el VALOR crudo hasta abrirse una vez**: las etiquetas las registran los `Select.Item` al montarse y el `Content` portaleado está cerrado. Rodeo: el `child` de `Select.Value`. | Y dos huecos anteriores que siguen abiertos: **`Box`/`Surface` `flex`/`grow` no hacen crecer a un hijo flex** (se rodea con tracks `1fr` de `Grid`) y **`Group` no apila** (usa `Flex direction={{ base: 'column', sm: 'row' }}`; `hero` todavía compone sus acciones con `Group`). --- ## La plantilla ya existe — cópiala, no la reinventes ```text src/uix/blocks/{kebab}/ ├── README.md # Función · Mapa de composición · Decisiones · Gaps ├── index.ts # export del compound + tipos ├── types.ts # props (todo contenido entra por snippets, B-5/B-7) └── {kebab}.svelte # composición: solo componentes del canon, sin CSS web/routes/blocks/{kebab}/ ├── +page.svelte # BlockDemo + controles vivos + pestañas de doc ├── {Name}Site.svelte # el block dentro de contenido REAL de producto └── preview/ ├── +layout@.svelte # `@` resetea el layout: la vista previa es su página └── +page.svelte # sirve {Name}Site leyendo la URL ``` Y luego: marcar `shipped: true` en `web/routes/blocks/_lib/catalog.ts` (raíl y galería leen esa única fuente) y `npm run blocks:check`. --- ## Reglas que ya costaron sangre (no las re-aprendas) - **El block se enseña A SANGRE en la página.** Nada entre el block y el borde: ni marco con relleno, ni caja con scroll, ni cromo pegajoso encima. Medido: un `Card` desplazaba 21px un header con `offset: 0` (su recipe pinta con `--card-padding-*`, que `padding={0}` de la capa Box no alcanza), y un `position: sticky` dentro de un div con scroll es un comportamiento que nadie vive. **Si un block se ancla a algo, se mide contra lo que se anclará en producción.** - **Verifica esperando la CONDICIÓN, nunca un timeout fijo.** Con `Form`+`Field`+ SIUM el dev server tarda ~2,5s en hidratar y un screenshot temprano fotografía el panel todavía invisible (`data-animation-pending` puesto). Esperar a que ese atributo desaparezca es la diferencia entre verificar y reportar un bug que no existe. - **Playwright headless SÍ sirve para foco y teclado.** El pane suspendido no (congela rAF y pierde `activeElement`), pero un `page.keyboard.type()` real dispara `:placeholder-shown` y `:focus-within`: así se verificó que la etiqueta flotante del `Field` sube al borde. `page.fill()` usa el setter nativo y NO los dispara. Un script del scratchpad debe importar `file:///G:/dev/svelte/vicen/node_modules/playwright/index.mjs` (no resuelve `playwright` por ruta relativa). - **Los anchos de dispositivo (375/768) van por la ruta `preview` en iframe**, y es opt-in: en dev, dos documentos sin empaquetar a la vez agotan las conexiones del navegador (`ERR_INSUFFICIENT_RESOURCES` mata las DOS páginas). - **Cada prop público, un control vivo** en la demo; los ejes de sección (tema, idioma, dirección, densidad) ya los da el shell, no los repitas. - **Un slot que se apaga se pasa como `undefined`, no se renderiza vacío.** Declara el snippet arriba y pásalo por prop (`signup={activo ? banda : undefined}`): así el `{#if}` del block quita también su hueco. Y **cuidado con el sombreado**: `{#snippet signup()}` pisa un prop llamado `signup` dentro del componente. - **Nada de backticks de markdown dentro de ``**: se ven literales. Lo que es código va en ``. - **Ojo con las variables de layout que heredan.** `justify` de `Group` se hereda a los clusters anidados (pon `justify` explícito) y las de `Box` YA no heredan desde 2026-07-23 (`docs/theming/changelog.md` §46). - **Un hueco del canon se registra, no se falsea.** Si al componer falta algo, va a los Gaps del block como candidato a canon y la demo usa lo que hay. - **Los números de layout se miden.** Tres defaults de `site-footer` (`container`, la razón de la rejilla, el gap de columnas) salieron mal a la primera y solo el navegador lo dijo. Si un default decide cuántas cosas caben en una fila, se comprueba con el contenido real. --- ## Deuda del arnés de demos (mía, sin tocar) - La galería (`web/routes/blocks/+layout@.svelte`) **arranca UIX en línea** en vez de usar `_lib/BootUix.svelte`, que es lo que usan los previews: la misma cadena escrita dos veces, y el motivo de que `reset.css` haya que importarlo en los dos sitios. - En `BlockDemo`, a ~1400px, el conmutador de anchos de dispositivo se solapa con el párrafo que lo precede. Idéntico en los 11 blocks → es del arnés, no de un block. --- ## Deuda a decisión tuya Siguen sin decidir, cada una entra por el contrato B con **fase 0 ligera** (mirar ≥2 catálogos del dossier y anotar adoptado/descartado en el README): 1. **Tabla de comparación** de features×planes en `pricing` (recurre en las refs; sería un hermano `pricing-table`). 2. **Cita en spotlight** en `testimonials` (la variante modal de TODAS las refs; el grid es minoría, 2 de 8 en TW). 3. **Lista estática 2/3 columnas** en `faq` (6 de 7 en TW no son acordeón). 4. **`form-in-hero`** (slot de alta al boletín dentro del hero). Resueltas ya: feature-split/alternante (F2.3b, con `Mockup`) · hero con fondo cover (layout `background`) · CTA que navega y parece botón (el `child` de `Button` entrega un snippet `content`; `Button` sigue sin `href` y `Link` sigue poseyendo la navegación — doctrina en el README de eidos Button §«CTA que navega»). --- ## Estado de gates al parar - `npm run blocks:check` **verde (11 blocks)**. - `svelte-check`: **sin errores propios** (73 en el repo, todos ajenos: `file-upload`, etc.). - `prettier`: limpio en mis ficheros del tier. - `vitest src/uix/contracts.test.ts`: **3 fallos AJENOS** (35 pasan) — escrituras DOM directas en soma, data-attrs hardcodeados y las claves camelCase de `aura` (`components.aura.announce.returnedStopped`, …). Ninguno apunta a `blocks/`.