You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/PLAN-blocks-quality.md

215 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# PLAN — Subir el listón de los blocks (análisis comparativo + plan de mejora)
> Encargo del usuario (2026-07-24): *«todos los bloques hechos hasta ahora son
> simplones… no hay animaciones, no hay disposiciones o diseños que los hagan
> sobresalir… la peor versión de todos los que se analizaron»*. Este documento
> audita los 7 blocks hechos contra las referencias del dossier
> (`RESEARCH-blocks-references.md`) **y contra el propio framework**, y fija el
> plan para que cada block gane la comparación en vez de empatarla.
---
## 1. Diagnóstico: qué compone realmente cada block (dato, no opinión)
Auditoría de imports reales en `src/uix/blocks/*/*.svelte`:
| Block | Componentes que compone |
|---|---|
| site-header | box · container · drawer · group · sticky |
| hero | box · container · grid · group · **heading** · section · stack · text |
| feature-grid | auto-grid · box · container · heading · section · stack · surface · text |
| feature-split | box · container · grid · group · heading · icon · section · stack · text |
| pricing | auto-grid · box · card · container · group · heading · icon · section · stack · text · toggle-group |
| testimonials | auto-grid · box · card · container · group · section · stack · text |
| faq | accordion · box · container · section · stack |
**Los 7 componen EXCLUSIVAMENTE la capa de layout** (`Box`/`Stack`/`Grid`/
`Group`/`Section`/`Container`) + tipografía base (`Text`/`Heading`) + `Card`/
`Surface`, más el componente de comportamiento que cada uno necesita
(`Drawer`, `Sticky`, `Accordion`, `ToggleGroup`).
**Uso de la capa expresiva del framework: CERO.** Ni un `Motion`, ni un
`Cascade`, ni un `Display`, ni un `TextGradient`, ni un `CountUp`, ni un `Aura`.
### El framework SÍ tiene esa capa — y no la usamos
| Componente del canon | Qué hace | Dónde debería estar |
|---|---|---|
| **`Display`** | *«eidos single-component **hero typography** primitive»* | **el hero** (usé `Heading`) |
| **`Cascade`** | escalona (stagger) la entrada/salida de sus hijos | feature-grid, testimonials, pricing, stats |
| **`Motion`** | animador de un elemento con presets (`scale-fade`, `slide-fade`, `spring-pop`…) | entradas de cualquier pieza |
| **`TextGradient`** | degradado animado sobre texto real; acepta `colors="aurora"` | titulares de hero/CTA |
| **`CountUp`** | cuenta con spring **al entrar en viewport**, locale-aware, respeta reduced-motion | stats-band, pricing, métricas |
| **`Mockup`** | cromo de navegador/teléfono (lo construí en F2.3b) | hero, feature-split, testimonials |
| **`Aura`** | escena ambiental | fondos de sección |
| `TextBlur` · `TextFocus` · `TextScramble` · `TextCircular` | familia de efectos de texto | acentos puntuales |
| `Carousel` · `Timeline` · `Metrics` · `Image` | — | testimonials, stats, content |
### Por qué salieron planos: la causa es ESTRUCTURAL, no de gusto
El contrato B prohíbe que un block traiga `.css` propio (D-BLK.2). Es una regla
buena — mantiene el tier componiendo en vez de pintando. Pero implica una cosa
que no vi a tiempo:
> **Un block solo puede ser tan expresivo como los componentes del canon que
> compone.**
Con solo primitivas de layout disponibles y **sin primitiva de scroll-reveal ni
de fondo decorativo**, el resultado plano era el único posible. Mi error no fue
"no decorar los blocks": fue **no detectar que faltaban primitivas en el canon y
construirlas** (como sí hice con `Mockup`), y dar por hecho un block que compone
bien pero no gana la comparación.
**Corolario que dirige el plan**: la mejora NO es maquillar 14 blocks uno a uno.
Es **construir 2–3 primitivas que faltan y aplicarlas sistemáticamente** — sube
todo el tier a la vez y queda arquitectónicamente limpio.
---
## 2. Los dos huecos del canon que bloquean el acabado
### Hueco 1 — No hay scroll-reveal
`Cascade` escalona, pero se dispara por `open` (disclosure), no por viewport.
Toda la maquinaria existe ya a nivel de art:
- `eidos.dom.observeIntersection(el, cb)` (adom)
- `src/arts/adom/is-in-viewport.svelte.ts` (primitiva reactiva)
- `eidos.dom.prefersReducedMotion`
`CountUp` la usa **en privado**. Nada la expone como composable. Sin esto, cada
sección aparece de golpe: el rasgo nº1 que separa a Linear/Vercel/Stripe de un
dump estático.
→ **Resuelto**: `Motion` gana `trigger="viewport"` (NO un componente nuevo — `Reveal` se creó y se retiró por ser un fork) y `Cascade` gana el mismo disparo.
### Hueco 2 — No hay fondo decorativo de sección
No existe `backdrop`/`pattern`/`glow`/`mesh`/`noise`/`spotlight` en el canon.
Solo hay **un** gradiente nombrado (`--gradient-aurora`) y los acabados de
`Surface`. Las referencias viven de esto (glow radial tras el hero, rejilla de
puntos, mesh, viñeta).
→ **Candidato a canon: `<Backdrop>`** (patrones + glow + mesh sobre tokens).
---
## 3. Comparativa por block: anatomía (dossier) + acabado (nuevo)
**A** = brecha de anatomía ya registrada en el dossier §P1.
**B** = brecha de acabado (nueva, la que motivó este plan).
**C** = superación que podemos meter y ninguna referencia puede.
| Block | A · anatomía que falta | B · acabado que falta | C · superación disponible |
|---|---|---|---|
| **hero** | presets de media ✔(Mockup) · **form-in-hero** · logo-strip como slot · video | `Display` en vez de `Heading` · `TextGradient` en el titular · `Backdrop` (glow/mesh) · reveal escalonado · parallax suave del mockup | tipografía fluida por tokens + dark/RTL/density gratis |
| **feature-grid** | variante en `Card` · numerada | `Cascade` al entrar · hover con elevación/acento en el item · iconos con más presencia | stagger con reduced-motion correcto |
| **feature-split** | banda de fondo por fila · media pegajosa | reveal por fila (media y copy con desfase) · `Backdrop` alterno | `Mockup` real (ninguna ref lo compone, lo dibujan a mano) |
| **pricing** | **tabla de comparación** · single-price | transición del precio al cambiar periodo (`Motion`) · destaque del featured (glow/escala) · `CountUp` en el importe | precio locale-aware (`FormatNumber`) + toggle con estado real |
| **testimonials** | **cita en spotlight** (la variante modal de TODAS las refs) | `Cascade` · logos de empresa · comillas decorativas · avatar con anillo | comillas locale-aware (territorio sin reclamar) |
| **faq** | **lista estática 2/3 col** (6 de 7 en TW) · cola de soporte como slot | transición de apertura con carácter · numeración/iconografía | teclado+ARIA del `Accordion` del canon |
| **site-header** | flyout ✔ · banner hermano | fondo con blur al pegarse (`[data-stuck]` ya lo da) · transición de la sombra | `data-stuck` = polyfill del `scroll-state()` futuro |
| **stats-band** *(pendiente)* | split-with-image · timeline | reveal + `Backdrop` | **`CountUp`: ninguna ref puede shippearlo** (markup estático) — la superación literal |
| **cta** *(pendiente)* | arreglo **justified** · split-with-media | `Backdrop` + gradiente de acabado | — |
| **newsletter** *(pendiente)* | nota de privacidad | estados (enviando/éxito) con `Motion` | validación real del `Form` del canon |
| **site-footer** *(pendiente)* | newsletter en footer · **selector de idioma** | — | el selector de idioma encaja con `langs` (ninguna ref lo tiene funcional) |
| **banner · team · contact · content-section** *(pendientes)* | — | al nivel nuevo desde el día 1 | — |
---
## 4. Plan
### F-Q0 · Primitivas que faltan en el canon *(la palanca)*
| # | Pieza | Por qué |
|---|---|---|
| Q0.1 | ~~`Reveal`~~ → **`Motion trigger="viewport"`** (+ `rootMargin`, `threshold`, `once`, `delay`) | HECHO. Se creó un `Reveal` aparte y se consolidó: era un fork de `Motion`, que ya era el animador de un elemento — solo le faltaba el CUÁNDO |
| Q0.2 | **`Cascade` por viewport** — `trigger="viewport"` además de `open` | HECHO |
| Q0.3 | **`Backdrop`** — fondo de sección: `glow` · `mesh` · `grid` · `dots`, todo sobre tokens | HECHO |
Los tres siguen el patrón `Mockup`: componente eidos + morfo mínimo, recipe
sobre tokens, cero color a mano.
### F-Q1 · Pasada de acabado a los 7 hechos
Por block: aplicar A (anatomía) + B (acabado) de la tabla §3. Orden sugerido por
impacto: **hero → pricing → feature-split → testimonials → feature-grid → faq →
site-header**. El hero primero como **prueba del nuevo listón**: si no gana
al lado de la referencia, se itera antes de replicar.
### F-Q2 · Los 7 pendientes, ya al nivel nuevo
stats-band (con `CountUp` — la superación literal) → cta → newsletter →
site-footer → banner → team → contact → content-section.
### F-Q3 · Página compuesta como prueba de aceptación
Todas las refs shippean páginas compuestas (TW 10 · Untitled 105). La nuestra
—los 14 blocks en una landing real— es el test de que el conjunto respira.
### Cambio en la definición de "hecho"
Un block **no** está hecho cuando compone bien y pasa `blocks:check`. Está hecho
cuando, **puesto al lado del mejor ejemplo de las referencias, gana**. Añadir al
checklist del contrato B: ¿tiene movimiento? ¿profundidad/fondo que lo
distinga? ¿una disposición que no sea la rejilla obvia? ¿tipografía con
presencia?
---
## 5. Riesgos
- **Sobre-animar**: el movimiento debe respetar `prefers-reduced-motion` (la
maquinaria ya lo hace) y no retrasar la lectura. Presets sobrios, no circo.
- **Romper el contrato B**: nada de `.css` en blocks — todo el acabado entra por
primitivas del canon. Si algo no se puede, es que falta una primitiva.
- **Regresiones**: `Reveal`/`Backdrop` tocan el canon → suite eidos + barrido de
demos, como en los fixes de `Grid` y `Accordion`.
---
## 6. Auditoría posterior (2026-07-29) — deuda que dejó esta pasada
Una auditoría multi-agente (7 dimensiones doctrinales + refutación adversarial)
confirmó **32 hallazgos** sobre el trabajo de la pasada. Lo corregido dentro del
tier queda en los README de cada block; lo que cae **fuera de `blocks/`** queda
registrado aquí como deuda, sin tocar:
| # | Dónde | Qué |
|---|---|---|
| A1 | `eidos/components/motion/motion.svelte` | con `trigger='viewport'` no estampa `data-state` hasta que dispara el observador, y su morfo lo declara `always`+`required`. `Cascade` —construido en la misma pasada— sí lo hace bien: estampa el estado y deja la espera en un attr aparte |
| A2 | `motion.css` · `cascade.css` | el `opacity: 0` de espera solo se levanta con la media query del SO; la preferencia de app proyectada como `data-motion='reduce'` NO lo levanta |
| A4 | `motion.svelte` (`leave`) | la salida no pasa por el motor: un preset JS no anima al salir y el nodo se retiene 200 ms |
| C7 | `motion.css` + su README | crear el recipe activó la regla de auditoría R-1.1 (sin selector raíz `[data-motion]`) y el README declara la excepción CONTRARIA («ships no CSS») y sigue diciendo «no JS engine» |
| C8 | `eidos/components/{mockup,backdrop}` | sin README (162 de 165 componentes lo tienen) |
| C10 | `docs/theming/motion.md` | no documenta el `trigger` nuevo ni la llamada al motor |
| D12 | `morfo/components/backdrop.ts` · `cascade.css` | citan `reveal.ts`, borrado; y `Cascade` emite `data-reveal-pending` — nombre de un componente que ya no existe — para el mismo concepto que `Motion` llama `data-animation-pending` |
| E14 | `motion.css:42` | `--motion-stagger-each-default` no existe en ninguna parte: el knob es ficción y siempre vale el literal 70ms. Ya existe el canónico `--motion-stagger` |
### Añadidos al construir `cta` (2026-07-30) — medidos en navegador
Tres cosas que salieron al componer F2.8, todas verificadas con sonda de
luminancia / `getComputedStyle`, no a ojo. Caen fuera de `blocks/`, así que
tampoco se han tocado:
| # | Dónde | Qué |
|---|---|---|
| F15 | `eidos/components/surface` | `variant='soft'` no puede acotar un panel: el track de `primary` mide `oklch(0.9932 0.0034 325.6)` contra un `--color-surface-default` de `oklch(0.9911 0 0)` — **0.002 L** — y `Surface` no tiene prop de borde. `Card outline` sí acota pero no acepta `gradient`, así que «panel sosegado con borde» no tiene primitivo. Por eso `cta` **eliminó** su prop `variant` |
| F16 | paleta (`contrast` slot) | la ranura de contraste es `#ffffff` en **todo** escalón sólido, así que un lienzo de luminancia media deja el cuerpo de texto por debajo de AA. Medido sobre el panel: `primary` 5.18 · `indigo` 5.21 · `plum` 4.75 (pasan en ambos modos) vs `neutral` 3.32 · `secondary` 3.30 · `slate` 3.30 · `teal` 3.07 (fallan en claro). La garantía de emparejamiento solo se cumple en los lienzos oscuros |
| F17 | `eidos/components/text` | `align` es **inerte** por defecto: el componente renderiza un `span` y `text-align` no hace nada sobre una caja inline. `align="center"` dejó la copia alineada a la izquierda dentro de un layout centrado sin avisar de nada. Se rodea con `as="p"`, pero el prop anuncia un efecto que no tiene hasta que el consumidor cambia el elemento |
### Añadidos al construir `newsletter` (2026-07-30) — el tier de formulario
| # | Dónde | Qué |
|---|---|---|
| F18 | fundación de `eidos` (`index.css`) | **No hay reset de modelo de caja.** Recetas como `[data-field-control]` declaran `inline-size: 100%` + `padding-inline`, así que bajo `content-box` el control mide **30px más que su contenedor**: en la fila `1fr auto` del newsletter el campo se metía por debajo del botón de envío. Medido: campo 480 / control 510 en la galería de blocks frente a 502 / 502 en los docs de componentes, cuyo `uix.css` resetea `box-sizing` bajo `[data-uix-docs]` (y `web/routes/active/styles.css` hace lo mismo). O sea: el framework **asume** que el app pone `border-box` y esa asunción solo está documentada porque app-land la cumple dos veces. Arreglado en mi lado con `web/routes/blocks/_lib/reset.css` (A/B sobre los 10 previews y 5 páginas de shell: cambia el newsletter y NADA más). La pregunta de canon es si la fundación debería poseerlo en vez de asumirlo |
| F19 | `soma/components/form` (`form.svelte` + `types.ts`) | **`onValidSubmit` / `onInvalidSubmit` son no-op silenciosos cuando se pasa un `form` ya construido.** El componente solo los reenvía al `createForm` que hace él mismo (la rama `defaults`); con un handle externo se ignoran sin aviso, aunque el tipo los documenta como «Called when validation passes». El envío validaba, limpiaba el error y no anunciaba nada. El block los quitó de su API: el handler va en `createForm` |
| F20 | `libs/forms` + `web/routes/uix/components/form` | Los mensajes de SIUM llegan como **idlangref** (`#?sium.errors.email|Must be a valid email address`). La vía soportada es `uix.langs.t(issue.message, issue.params)` — verificado, resuelve el catálogo («Debe ser una dirección de correo válida»), no el fallback inglés. Pero la demo de docs del propio `Form` **parte la cadena a mano** tras el `|` con un helper local `fallbackMessage`, así que el único ejemplo del repo enseña el patrón equivocado y siempre muestra inglés |
### Añadidos al construir `site-footer` (2026-07-30)
| # | Dónde | Qué |
|---|---|---|
| F21 | `eidos/components/box` (y todo lo construido sobre él) | **Los primitivos de layout no pueden cambiar de elemento.** `Text` y `Heading` aceptan `as`; `Box` —y por tanto `Stack`, `Flex`, `Grid`, `Group`, `Wrap`, `Container`, `Section`— renderiza un `<div>` fijo. Consecuencia concreta: una columna de enlaces de pie no puede ser `<ul>/<li>`, que es como la marcan las referencias. Un block solo puede elegir entre divs o escribir markup que luego no puede estilar (contrato B). La asimetría (tipográficos sí, layout no) ES el hallazgo |
| F22 | `soma/components/select` | **Un `Select` controlado muestra el VALOR crudo hasta que se abre una vez.** `getDisplayText()` resuelve contra un registro de etiquetas que llenan los `Select.Item` AL MONTARSE; con el `Content` dentro de `Select.Portal` y cerrado no hay ninguno montado, así que `value=['es']` pinta «es» en vez de «Español». Se rodea con el `child` de `Select.Value`, pero el caso «select controlado que aún no se ha abierto» es el más común de todos |

Powered by TurnKey Linux.