# CONTINUE — F2 blocks de sitio (handoff 2026-07-23) Estado al parar: **F1 CERRADA (8/8 componentes canon)** y **F2.1 `site-header` HECHA** — el primer block del tier, que además fijó la superficie de demo para los 13 que quedan. Plan maestro: `docs/process/PLAN-blocks.md` (§F2 y el registro §7). Todo commiteado y pusheado a `gita/alpha-0.1-sec-dom`; último commit `ae7b4f8c3`. ## Lo siguiente **F2.5 `testimonials`** (ficha en `PLAN-blocks.md` §F2.5). COMPOUND (`.Item` repite): `Section` + `AutoGrid` + `Card` + `Avatar` + `Text` + `Group`. v1 = grid; la cita única en spotlight (logo+quote+avatar) es la brecha del dossier (scope-approval). Después: faq → stats-band → cta → newsletter → site-footer → banner → team → contact → content-section. **F2.4 `pricing` HECHO** — primer compound CON CONTEXTO del tier (el `Switch` escribe el periodo, los `PlanPrice` lo leen). Precedente para cualquier block cuyas partes COORDINEN: contexto reactivo vía `context.ts` (getter sobre un `$bindable`), como `CardGroup`. El block NUNCA formatea moneda — la app compone `FormatNumber` en los snippets `monthly`/`annual`. **F2.1 site-header · F2.2 hero · F2.3 feature-grid · F2.3b feature-split HECHOS**, **+ primitivo canon `Mockup`** (`$uix/eidos/components/mockup`: `chrome` browser/ phone/plain + `url`) — úsalo para toda media enmarcada en los blocks que vienen (pricing screenshots, testimonials, etc.), NO falsees un screenshot por demo. Regla de forma del tier (NO re-decidir): **compound solo cuando las partes COORDINAN o SE REPITEN** (feature-grid.Item, pricing.Plan, faq.Item — partes que el app itera); **slots de snippet para secciones de layout** (hero, site-header, cta, stats-band). Lecciones de feature-grid que ahorran sangre en los compound que vienen: - **Sub-componentes de bloque extienden los props del canon que envuelven** (`BoxProps`, `StackProps`, `HeadingProps`…), **nunca `HTMLAttributes`** — el `style: string|null` del atributo HTML crudo choca al hacer spread en el canon (+ "union type too complex"). - **`.Items`/`.List` como envoltorio de la rejilla** deja la cabecera fuera sin hacks de `grid-column`. - **Chips de icono: `Surface variant="solid"`**, no `soft` (el soft en claro es casi blanco, oklch 0.99, invisible). - **Un slot de snippet cuyo nombre sea un atributo HTML** (`title`, `background`, `media`…) colisiona → `string & Snippet`. Solución: `Omit` o nombra el slot distinto (`backdrop`). Pasó en `hero`. - **svelte-check filtrado por ruta con backslashes**: escapa bien o filtra por nombre de fichero (`rg -i "HeroSite"`), no por `routes\\blocks\\…` — un patrón mal escapado oculta errores propios. ## Deuda a decisión tuya (nuevo) **Feature-split / alternante** (texto junto a screenshot, lados alternos) — la brecha nº1 de F2 del dossier. Es otra disposición (filas de 2 columnas, no rejilla de iconos): candidato a block hermano `feature-split`. En los Gaps de `feature-grid`. Sin decidir. Cada uno entra por el contrato B (`docs/architecture/blocks.md`) con **fase 0 ligera obligatoria**: mirar el equivalente en ≥2 catálogos del dossier (`docs/process/RESEARCH-blocks-references.md`) y anotar en el README del block qué se adopta y qué se descarta. ## La plantilla ya existe — cópiala, no la reinventes Un block terminado son estos ficheros (ejemplo real: `site-header`): ```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.** - **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. - **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. ## Deuda declarada (decisiones tuyas pendientes) 1. ~~**CTA que navega y parece botón**~~ — **RESUELTO 2026-07-23** por composición, no por prop nueva: el `child` (asChild) de `Button` entrega ahora un snippet `content`, así que `{@render content()}` recibe la pintura sólida COMPLETA (icono · etiqueta · endIcon · spinner) y soma deja de estampar `type` en un elemento que no es suyo. `Button` sigue sin `href` y `Link` sigue poseyendo la navegación. Doctrina: README de eidos Button §«CTA que navega». Úsalo tal cual en el `hero` (su CTA primario es exactamente esto). 2. **`pricing`, `feature-grid`** tienen brechas del dossier §P1 que el plan v1 no cubre (tabla comparativa de precios, split/alternante texto-screenshot). Cada fase 0 las presenta como scope-approval, no se deciden solas. (La de `hero` —fondo cover— **ya se decidió y está hecha**: layout `background`.) 3. **`Box`/`Surface` `flex`/`grow` no crecen un hijo flex** (encontrado en `hero`): `` se quedó a 0-width (`flex: 0 1 auto`, el `--box-flex` no surtió efecto); ningún componente shipped usa la prop. La demo usó `Grid` (tracks `1fr`). Revisar el cableado end-to-end de `--box-flex`/`--box-grow`. ## Estado de gates al parar - `npm run blocks:check` verde (**2 blocks**). - `npm run check` sin errores propios (la deuda restante es ajena; p.ej. `web/routes/demos/heroscrolling` es de otra sesión). - `vitest src/uix/eidos` 353/353 · `vitest src/uix/morfo` 114/114. - `contracts.test.ts`: 3 fallos AJENOS conocidos (menubar DOM-write · radio-group `data-ready` · claves camelCase de `aura`), de sesiones paralelas.