8.1 KiB
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.7 stats-band (ficha en PLAN-blocks.md §F2.7). Compone Section +
Group/AutoGrid + Metrics + CountUp + Text. .Stat = valor + etiqueta;
CountUp opt-in por prop. v1 = banda 2–4 stats; los formatos numéricos llegan ya
formateados o vía FormatNumber del app (como pricing, el block no formatea). El
dossier dice: ninguna ref puede shippear count-up (markup estático) → es nuestra
superación literal. LEER Metrics/CountUp antes. Después: cta → newsletter →
site-footer → banner → team → contact → content-section.
F2.4 pricing · F2.5 testimonials · F2.6 faq HECHOS. faq = proxy fino de un
componente del canon (Accordion): cuando un block envuelve un componente
interactivo, LEE su API y la pasa tal cual (.List ES el Accordion), no
reinventa el comportamiento — solo ahorra andamiaje con .Item. Mismo criterio
para stats-band con Metrics/CountUp.
F2.4 pricing + F2.5 testimonials HECHOS. pricing = primer compound CON
CONTEXTO (el Switch escribe el periodo, los PlanPrice lo leen; contexto
reactivo vía context.ts, getter sobre $bindable, como CardGroup; el block
NUNCA formatea moneda). testimonials = compound SIN contexto (.Item repite).
Lecciones nuevas para los que vienen:
- Escalas donor activadas =
green/indigo/orange/plum/teal. Una escala NO activada (cyan/ruby/amber/jade) encolorcae en SILENCIO aprimary. Usa las activadas. CardNO tiene variantsurface(solosoft/solid/outline/ghost);soft neutrales casi invisible en claro → usaoutlinepara tarjetas que deben leerse.
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…), nuncaHTMLAttributes— elstyle: string|nulldel atributo HTML crudo choca al hacer spread en el canon (+ "union type too complex"). .Items/.Listcomo envoltorio de la rejilla deja la cabecera fuera sin hacks degrid-column.- Chips de icono:
Surface variant="solid", nosoft(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<HTMLAttributes, '…' | 'title'>o nombra el slot distinto (backdrop). Pasó enhero. - svelte-check filtrado por ruta con backslashes: escapa bien o filtra por
nombre de fichero (
rg -i "HeroSite"), no porroutes\\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):
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
Carddesplazaba 21px un header conoffset: 0(su recipe pinta con--card-padding-*, quepadding={0}de la capa Box no alcanza), y unposition: stickydentro 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
previewen iframe, y es opt-in: en dev, dos documentos sin empaquetar a la vez agotan las conexiones del navegador (ERR_INSUFFICIENT_RESOURCESmata 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
<Text>: se ven literales. Lo que es código va en<Code>. - Ojo con las variables de layout que heredan.
justifydeGroupse hereda a los clusters anidados (ponjustifyexplícito) y las deBoxYA 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)
CTA que navega y parece botón— RESUELTO 2026-07-23 por composición, no por prop nueva: elchild(asChild) deButtonentrega ahora un snippetcontent, así que<a href {...props}>{@render content()}</a>recibe la pintura sólida COMPLETA (icono · etiqueta · endIcon · spinner) y soma deja de estampartypeen un elemento que no es suyo.Buttonsigue sinhrefyLinksigue poseyendo la navegación. Doctrina: README de eidos Button §«CTA que navega». Úsalo tal cual en elhero(su CTA primario es exactamente esto).pricing,feature-gridtienen 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 dehero—fondo cover— ya se decidió y está hecha: layoutbackground.)Box/Surfaceflex/growno crecen un hijo flex (encontrado enhero):<Surface flex={1}>se quedó a 0-width (flex: 0 1 auto, el--box-flexno surtió efecto); ningún componente shipped usa la prop. La demo usóGrid(tracks1fr). Revisar el cableado end-to-end de--box-flex/--box-grow.
Estado de gates al parar
npm run blocks:checkverde (2 blocks).npm run checksin errores propios (la deuda restante es ajena; p.ej.web/routes/demos/heroscrollinges de otra sesión).vitest src/uix/eidos353/353 ·vitest src/uix/morfo114/114.contracts.test.ts: 3 fallos AJENOS conocidos (menubar DOM-write · radio-groupdata-ready· claves camelCase deaura), de sesiones paralelas.