114 KiB
PLAN — Tier blocks: composición reutilizable (infraestructura + componentes base + catálogo)
Kickoff para sesión nueva: "Lee
docs/process/PLAN-blocks.mdy continúa la fase que toque." Decisión de usuario (2026-07-21): existe un tier nuevoblocks— conjuntos de componentes desempeñando una función (cabecera sticky, hero, footer, app-shell…). Este plan es autosuficiente: cada fase lista QUÉ leer, QUÉ producir y CON QUÉ guard se verifica. Un agente no debe descubrir la doctrina por arqueología — este documento la enlaza toda.Antes de escribir código de F0: presentar al usuario las decisiones D-BLK de §2 (AskUserQuestion o tabla en chat) y obtener firma. Las propuestas de este plan son eso — propuestas razonadas, no decisiones tomadas.
0. Contexto y estado
- Origen: análisis del ecosistema (sesión 2026-07-21). Diagnóstico: el
catálogo de primitivas es excepcional (~140 componentes eidos, ~95 con soma);
la brecha está en (a) piezas de contenido/estado de página y (b) el nivel de
composición. Iniciativa registrada en
docs/next-features.md§8. - Precedentes en el repo: la familia
chat-*es un "bloque" construido como componentes canónicos (siguió la ruta de 9 fases porque cada pieza tiene contrato real);picker-shelles un chasis compartido; el tierpacks(docs/architecture/packs.md) ya resolvió la pregunta "¿cómo vive un tier fuera del canon?" — este plan lo usa de espejo. - Estado: F0 pendiente (nada construido). Actualizar esta tabla al cerrar
cada tanda, estilo
PLAN-component-coherence.md.
| Fase | Contenido | Estado |
|---|---|---|
| F0 | Infraestructura del tier: doctrina + alias + guard + rutas demo | HECHA 2026-07-21 (F0.1–F0.7; cross-ref en comparison.md omitido a propósito — sin aporte hasta que exista catálogo) |
| F1 | 8 componentes base del CANON que los blocks necesitan (7 + nav-tree por E-1) |
CERRADA — 8/8 HECHAS (empty-state · result · callout · sticky · prose · anchor-nav · nav-tree · sidebar, PASS las ocho; ver registro) |
| F2 | Blocks de sitio (14: 10 + banner·team·contact·content-section por E-3) | pendiente (F2 solo requiere F1.1) |
| F3 | Blocks de aplicación (10) | pendiente |
| F4 | Blocks de docs (3) | pendiente |
| F5 | Backlog condicionado (componentes media/mobile + blocks diferidos) | pendiente |
1. Qué es un block (doctrina propuesta — aterriza en docs/architecture/blocks.md en F0)
Un block es una composición nombrada de componentes del canon que desempeña una función de página: no aporta primitivas nuevas, aporta ensamblaje correcto (layout, landmarks, jerarquía de headings, responsive, puntos de contenido). Consume el framework; el framework nunca lo referencia.
La tabla de tiers queda:
| Tier | Valor | Contrato | Entra por |
|---|---|---|---|
Canon (src/uix/) |
densidad de contrato (eventos, ARIA, teclado, tokens que otros consumen) | morfo + matriz de aceptación | ruta de 9 fases (docs/building-a-component.md) |
Packs (src/packs/) |
decoración parametrizada de hoja | contrato P | packs-check |
Blocks (src/uix/blocks/) |
composición de función de página | contrato B (§3) | blocks-check |
Regla de admisión (espejo de la de packs): si al construir un block hace
falta comportamiento nuevo con superficie de contrato — un evento real, una
máquina de estados, un data-attr que el CSS necesita seleccionar, una
obligación a11y de widget — esa pieza se construye ANTES como componente
canónico por la ruta de 9 fases, y el block la compone. Nunca se le crece
el privilegio al block. (Es exactamente la regla "compose existing
components; flag gaps" de docs/guides/component-guide.md §4, elevada a
frontera de tier.) La F1 de este plan existe porque ese triage ya está hecho:
los 7 gaps detectados se construyen primero.
Lo que un block SÍ posee semánticamente: landmarks y estructura de
documento (header/nav/main/aside/footer, jerarquía h1–h6, skip-link,
aria-label de región). Los componentes no pueden saber el contexto de
página; los blocks sí. Es su única superficie a11y propia.
Un block PUEDE tener estado de vista local (pestaña activa, toggle mensual/anual) usando los props/eventos públicos de los componentes que compone. Lo que NO puede es materializar ese estado con DOM/CSS propio que requiera contrato (→ regla de admisión).
2. Decisiones D-BLK — FIRMADAS 2026-07-21
Repasadas y firmadas por el usuario en el kickoff (F0.1 HECHA). D-BLK.1
quedó enmendada respecto a la propuesta original del plan (que proponía
src/blocks/); el resto se firmó tal como estaba propuesto. D-BLK.3/4/5
derivan de doctrina ya vigente y se firmaron por no-objeción.
| # | Decisión | FIRMADO |
|---|---|---|
| D-BLK.1 | Ubicación y alias | src/uix/blocks/ + alias $blocks (decisión de usuario: el tier es UI y vive junto a las capas). La prueba de encapsulación se conserva íntegra: borrar src/uix/blocks/ deja npm run check verde y NADA del canon (morfo/soma/sema/eidos/active-uix/langs) lo importa — blocks es un tier bajo src/uix/, no una quinta capa. |
| D-BLK.2 | Estilos | Layout-components-first: el layout se hace componiendo Container/Section/Stack/Flex/Grid/AutoGrid/Wrap/Group/Separator/AspectRatio/Surface y sus props. Un block NO trae .css propio; un <style> scoped puntual exige justificación en su README y nunca selecciona internals de componentes compuestos. |
| D-BLK.3 | API | Componente compuesto con partes anidadas: <SiteHeader> / <SiteHeader.Nav> / <SiteHeader.Actions>; contenido SIEMPRE por children, nunca árboles de datos (items={...} solo donde el componente canónico compuesto ya es data-driven). (Derivada de la regla compositional-not-data-driven ya vigente.) |
| D-BLK.4 | Demos | web/routes/blocks/{kebab}/+page.svelte + galería índice en web/routes/blocks/. (La ruta web/routes/alpha/ sigue TERMINADA y prohibida — no tocarla.) |
| D-BLK.5 | Idiomas/strings | Un block no posee NINGÚN string visible: todo texto llega del app como children/props. Si un string parece inevitable, es superficie de contrato → lo posee el componente canónico subyacente (vía texts: del morfo + langs). (Consecuencia mecánica de B-1: sin morfo no hay texts:.) |
| D-BLK.6 | Servicios | v1 sin servicios: los blocks NO consumen uix.prefs/langs/eidos directamente; el cableado (tema/idioma, submit de auth, transporte) llega como handlers/props del app. Revisable si ≥2 blocks demuestran necesidad real (misma vara que la 2-de-3). |
| D-BLK.7 | Naming F1 | sticky · anchor-nav · empty-state · result · callout · prose · sidebar — confirmados. (Los matices de naming siguen revisables en la fase 0 de cada uno, como toda fase 0.) |
Cualquier enmienda futura a una D-BLK se registra aquí con fecha ANTES de seguir construyendo (regla dura: los desvíos de alcance se declaran, nunca en silencio).
3. El contrato B (suelo de calidad de un block — guard: blocks-check)
| B | Obligación |
|---|---|
| B-1 | Sin morfo, sin pack sema, sin fila en component:audit. Un block es composición; el comportamiento con contrato se promociona al canon ANTES (regla de admisión §1). |
| B-2 | Todo elemento interactivo es un componente eidos del catálogo (Button, Link, Field, …). Elementos nativos interactivos crudos (button/input/select/textarea/a) = error de blocks-check. (Excepción única: el HTML que Prose recibe ya renderizado — ese contenido es del app.) |
| B-3 | Los componentes compuestos se consumen AS-IS por sus props públicos (variant/size/color/…). Prohibido re-estilizar sus internals desde el block (ni CSS ni style=). Los colores son siempre roles/tokens vía props — un block no decide color fuera del sistema. |
| B-4 | Dependencia unidireccional: src/uix/blocks/* importa $uix, $adom y arts públicos; nada del canon (src/uix/{morfo,soma,sema,eidos,active-uix,langs}) ni de src/{arts,libs,packs} importa de src/uix/blocks/. Borrar el tier deja check verde — la prueba de encapsulación se mantiene aunque viva bajo src/uix/. Entre blocks tampoco se importa (B-10). |
| B-5 | Contenido por composición (children/snippets). Nunca root={tree} ni props-árbol propias. |
| B-6 | Responsive con los mecanismos del framework (props responsive de los componentes de layout, breakpoints canónicos). Cero matchMedia/listeners propios — si hiciera falta observar algo, es señal de componente canónico (→ admisión). |
| B-7 | Cero strings propios (D-BLK.5). |
| B-8 | Landmarks correctos: elemento sectioning + aria-label/aria-labelledby cuando hay más de un landmark del mismo tipo; jerarquía de headings coherente y documentada en el README del block (qué nivel emite y cómo se ajusta). |
| B-9 | Cada block: README.md (secciones: Función · Mapa de composición — qué componentes canónicos usa y con qué props — · Decisiones · Gaps-con-disposición) + demo con profundidad de testbed (cada prop pública = control vivo; guía: docs/guides/demo-authoring.md, adaptada — sin las 9 tabs completas de componente, mínimo: escena realista + panel de props + código copiable). |
| B-10 | Un block no importa otro block. Si dos blocks comparten estructura, la pieza compartida o es un componente canónico o se duplica conscientemente (anotado en Gaps). Excepción declarada: los shells (app-shell, docs-shell) SÍ componen blocks/componentes de F1 por diseño — se lista explícitamente en su README. |
| B-11 | Motion: solo vía los props motion/presets de los componentes compuestos o Cascade para coreografía de entrada. Cero @keyframes/transitions propias (la regla R-4.5 del canon aplica moralmente aunque el audit no corra aquí). |
blocks-check (F0.5) verifica mecánicamente: B-2 (AST/regex de elementos
nativos interactivos), B-4 (dirección de imports), B-1 (no hay ficheros bajo
src/uix/morfo/components/ reclamados por blocks; no imports de
sema/components), D-BLK.2 (no .css bajo src/uix/blocks/; <style> solo
con /* justified: … */), B-9 (README + ruta demo existen), B-10 (imports
entre blocks solo en la allowlist de shells).
4. Reglas de trabajo (TODAS las sesiones de este plan)
Lectura obligatoria antes de tocar nada (leer los docs directamente — nunca delegar la lectura a agentes):
- Siempre:
CLAUDE.md(llega solo) · este plan ·docs/README.md(mapa). - F1 (componentes canon):
docs/building-a-component.md— LA puerta; cada fase de la ruta nombra su doc y su guard. No saltarse la fase 0 (tabla comparativa vs ≥3 referencias; cada ❌/⚠️ del scope recibe decisión del usuario ANTES de construir). - F2–F4 (blocks):
docs/architecture/blocks.md(existirá tras F0) + los README de CADA componente que el block compone (el mapa de composición se escribe leyendo, no de memoria) + referencias de blocks equivalentes (shadcn blocks · Tailwind UI/Plus · Flowbite blocks · PrimeBlocks · Relume) — comparativa ANTES de diseñar y al declarar done. - Dossier de referencia (2026-07-21): TODA fase 0 (F1–F4) contrasta
contra
docs/process/RESEARCH-blocks-references.mdANTES de diseñar — 6 pistas de investigación con suelos de paridad a nivel de prop, pitfalls y ángulos de superación por ítem. La fase 0 verifica contra el dossier (y solo investiga de cero lo que el dossier no cubra); las brechas de suelo que el dossier señala para el ítem se resuelven en su scope-approval.
Proceso por tanda (una tanda = un componente o un block):
git reset -q+ verificar HEAD (sesiones concurrentes; NUNCA amend).- Fase 0 del ítem: comparativa + scope al usuario si hay decisiones.
- Construir. UN fichero → verificar → resto (no-cascade). Componer, jamás re-implementar; gap detectado = FLAG al usuario, no workaround inline.
- Verificar:
npm run check+ scope vitest del ítem + guard del tier (component:audit --only {kebab}para F1 ·blocks-checkpara F2+) + navegador de verdad: screenshot y MIRARLO, claro Y oscuro (colorScheme:'dark'), móvil y desktop para blocks (resize 375/1280). - Demo con profundidad de testbed (B-9); docs del framework en el MISMO pase (README del ítem + mapa si procede).
- Commit (convención viva del repo):
uix({kebab}): …para F1,blocks({kebab}): …para F2+,docs(blocks): …para doctrina. Stage SOLO los paths propios (nuncagit add -A; excluirwords/,palabras/,web/routes/alpha/). - Actualizar la tabla de estado de este plan (y
next-features.md§8 al cerrar cada fase).
Prohibiciones: no tocar palabras/, chronos/, media-player
(foráneos/WIP — componerlos solo cuando estén landed; hoy chronos NO lo
está); no crear servicios/mocks falsos en tests (instancias reales vía
createActiveUix); no --no-verify; no borrar nada sin instrucción
explícita; responder en castellano, código y docs en inglés.
F0 — Infraestructura del tier
Objetivo: que exista el tier con doctrina, guard y sitio donde vivir — vacío pero verde.
| Paso | Producir | Verificación |
|---|---|---|
| F0.1 | Firma D-BLK (§2) con el usuario; enmendar el plan si procede | HECHA 2026-07-21 — firmas en §2 (D-BLK.1 enmendada: src/uix/blocks/) |
| F0.2 | docs/architecture/blocks.md — la doctrina de §1 + §3 en formato espejo de packs.md (frontmatter E1, regla de admisión, hard boundaries, contrato B, "promotion path" = la F1 como ejemplo vivido). Enlazar sin copiar: canon → CANON.md, ruta → building-a-component.md |
npm run docs:check (links) |
| F0.3 | Alias $blocks → src/uix/blocks en el const aliases de vite.config.ts (fuente de verdad) + svelte.config.js en sync + fila en la tabla de aliases de CLAUDE.md |
npm run check |
| F0.4 | src/uix/blocks/README.md — mapa del tier (inventario vivo = el árbol, como packs) + template de README de block (B-9) |
— |
| F0.5 | scripts/blocks-check.ts + npm script blocks:check — los checks mecánicos listados en §3. Espejo estructural de packs-check. Test negativo: un fixture con <button> crudo debe fallar |
npm run blocks:check verde en tier vacío + test negativo rojo |
| F0.6 | web/routes/blocks/+page.svelte — galería índice (dogfooding: componer Container/Section/Card/… del propio catálogo para la galería) |
navegador claro/oscuro |
| F0.7 | Cablear docs: fila E1 en el mapa de docs/README.md (architecture/blocks.md) + nota del tier en el diagrama de arquitectura de CLAUDE.md (línea blocks/ → …) + cross-ref en docs/comparison.md si aporta |
npm run docs:check |
Cierre F0: check + docs:check + blocks:check verdes; galería
renderiza vacía con mensaje de "en construcción" compuesto con el catálogo.
CERRADA 2026-07-21 — evidencia: docs:check 0/0 (511 docs);
svelte-check 76E/51W = baseline exacto (los archivos nuevos compilan
limpios; los 76 son deuda foránea preexistente del árbol sin commitear);
blocks:check verde (self-test 10 fixtures OK — el <button> crudo SE
detecta — y 5 532 archivos de canon/arts/libs/packs escaneados sin
violaciones de dirección). Galería verificada en navegador vía árbol de
accesibilidad + estilos computados en AMBOS modos (light: base-light, h1
oklch(0.24…); dark: base-dark, h1 oklch(0.95…)); la captura de píxeles
del panel embebido expiró (renderer suspendido en segundo plano — clase
conocida) — pendiente de un vistazo humano o Playwright en la primera
sesión F1. El listener prefers-color-scheme del layout funciona al boot;
el evento change en vivo no dispara con el panel suspendido (peculiaridad
del entorno, mismo patrón raw-matchMedia que el layout de /uix).
F1 — Componentes base del CANON (8)
Estos NO son blocks: entran por src/uix/{morfo,soma,sema,eidos} siguiendo
la ruta completa de 9 fases de docs/building-a-component.md (fase 0
Decide → 8 Acceptance), con component:audit --only {kebab} como oráculo.
Las fichas siguientes NO sustituyen la ruta — la parametrizan: dan el gap, la
membresía esperada, el boceto de morfo, las referencias mínimas de la fase 0
y las trampas conocidas del ecosistema que aplican. El vocabulario cerrado
(archetypes, familias, verbos, intents, holds) se toma SIEMPRE de
docs/canon/vocabularies.md (generado del código) — las fichas nombran
candidatos, el builder valida contra la lista real.
Orden recomendado: F1.2 → F1.3 → F1.4 (display, baratos, establecen ritmo) → F1.1 (desbloquea F2) → F1.5 → F1.6 → F1.8 → F1.7 (desbloquean F4/F3).
E-2 firmada (2026-07-21): los v1 de F1–F4 se dimensionan al suelo de paridad del dossier (lo que ≥2 referencias convergen) — cada fase 0 trae la subida concreta al scope-approval; quedarse bajo suelo exige decisión registrada, nunca omisión.
F1.1 sticky — afijado con estado
- Gap: wrapper
position:stickyque SABE cuándo está afijado (data-stuck) para que la cabecera cambie elevación/fondo al pegarse. - Membresía: soma + eidos (comportamiento real: observación + estado).
- Fase 0, comparar: AntD
Affix· MantineAffix· la técnica sentinel con IntersectionObserver (CSS-Tricks/web.dev) · shadcn (no lo tiene — anotarlo como diferencial). - Morfo (boceto): parts
provider(el contenedor sticky) +sentinel(1px observado,aria-hidden); data:data-stuck(presente/ausente),data-edge(top | bottom). Sin eventos v1 (afijarse es hecho de layout, no ocurrencia perceptiva del usuario — si algún consumidor pide señal sema, se revisa con el criterio D.4). Sin keyboard, sintexts. Partes display → sinarchetype(un archetype interactivo arrastra estilos de item; y el archetypecontentpisaposition— justo lo que sticky no puede permitirse). - Soma: provider observa el sentinel vía
dom.observe(IntersectionObserver por adom) — JAMÁS scroll listener +getBoundingClientRectsíncrono (regla de reflow: lecturas de layout solo post-layout víadom.measure/rAF).data-stucklo escriben los effects (dom.apply), único escritor. - Eidos: wrapper fino; tokens
--sticky-top-offset/--sticky-z-index(alias de la escala--z-index-*semántica, no número crudo). La receta NO decide sombra/fondo del contenido — eso lo hace el consumidor seleccionando su propio estado visual condata-stuckpresente (documentar el patrón en el README). - Demo: página larga con header/toolbar afijable, chip mostrando el estado, edge top y bottom.
- Trampas:
mergePropsclobberea stamps — attrs visuales del wrapper fuera del morfo salvo que crucen a soma (aquídata-stuckSÍ es soma).
F1.2 empty-state — estado vacío
- Gap: patrón universal icono/título/descripción/acción para listas y paneles sin datos. Hoy no existe nada.
- Membresía: morfo + eidos, sin provider soma (display puro; hay
precedente sancionado:
metricsesscope: ['sema','eidos']sin soma). Morfo-first aplica igual: hasta las hojas llevan morfo. - Fase 0, comparar: Chakra
EmptyState· AntDEmpty· HeroUI · patrones de empty state de Material. - Morfo (boceto): parts
provider+media(icon o ilustración) +title+description+actions. Sin eventos, sin keyboard, sin archetype en partes display.texts:NO — los strings los trae el app (es contenido, no chrome del componente). - Eidos: receta pequeña espejo de la más cercana ya enviada (estudiar
banner/cardantes de escribir una línea); centrado, spacing tokenizado--empty-state-*, tamaño vía canonsizesi aporta (probablemente solosm/md).actionscomponeButtondel catálogo vía children. - Demo: en contexto real — un
table/grid-listsin filas mostrando el empty-state, más la escena aislada con controles.
F1.3 result — página de resultado
- Gap: estado terminal de página/flujo (éxito, error, 403/404/500).
Pareja de
empty-state, distinto rol: cierra un flujo, no describe ausencia de datos. - Membresía: como F1.2 (morfo + eidos display).
- Fase 0, comparar: AntD
Result· patrones de error page de Tailwind UI · HeroUI. - Morfo (boceto): parts
provider+media+title+description+actions+extra. Propstatus→data-status(success | error | info | forbidden | not-found | server-error). Cuidado doctrinal:data-statusNO es el intent perceptivo — si en algún momento gana eventos con evaluación, el intent viaja porfromProp:intentdel morfo, no reciclando status (regla data-color ≠ perceptual-intent). v1 sin eventos. - Eidos: color del media por rol canónico según status (roles, no hex); iconografía por status con override por children.
- Demo: los 6 status + composición con
Buttonhome/back.
F1.4 callout — admonición inline
- Gap: aviso DENTRO del contenido (info/tip/aviso/peligro).
Banneres el anuncio de página; esto es la nota de documento — imprescindible paraprosey el docs-shell. - Membresía: morfo + eidos display (sin soma; variante dismissible se DIFIERE — si se pidiera, el dismiss es evento real → soma + sema en esa pasada, no antes).
- Fase 0, comparar: Radix Themes
Callout· shadcnAlert· admonitions de Docusaurus/Starlight ·bannerpropio (leer su README: qué decisiones ya están tomadas para avisos y cuáles NO trasladan). - Morfo (boceto): parts
provider(rolenote) +icon+title+content. La evaluación ES semántica aquí: propintentrestringido a un subset canónico (candidatos:neutral | affirm | risk | threat; validar contravocabularies.md) estampado víafromProp:intent— así eidos tiñe con la MISMA mecánica evaluativa del sistema, no con un enum paralelo inventado. - Eidos: tinte por intent usando roles/escalas (fondo suave + borde +
icono saturado — estudiar cómo tiñe
bannere imitar la mecánica); tipografía del canon;--callout-*para spacing/radius. - Demo: los 4 intents × con/sin título × con contenido multilínea, e
incrustado en un texto largo (anticipo de
prose).
F1.5 prose — contenido largo estilizado
- Gap: contenedor que estiliza HTML/markdown renderizado (h1–h6, p, listas, blockquote, tabla, código, img, hr) con los tokens del tema. Sin esto no hay docs ni blog.
- Membresía: morfo mínimo + eidos (sin soma). El morfo declara UNA part
provider; el trabajo vive en la receta. - Fase 0, comparar: Tailwind Typography (
prose) — el patrón de referencia — · MantineTypographyStylesProvider· Radix Themes. - Norma que lo hace legal: los selectores de elemento descendientes
(
[data-prose] h2,[data-prose] ul…) son selectores estructurales bajo una parte del morfo — sancionados por la norma S1 del eidos-lint (hook = data-attr del morfo o estructural bajo parte; clases NO). - Eidos: LA receta grande del grupo. Reglas: tipografía SOLO vía
primitivos del canon (
--font-*, escala tipográfica; cero literales — y los proporcionales que hagan falta con/* literal: */justificado); medida de lectura--prose-max-width(~65ch) tokenizada;code/prealineados con los tokens decode/code-block(leer sus recetas ANTES; si hay que duplicar valores, es un gap a flag, no un copy-paste); imágenesmax-width:100%; tablas con overflow propio. El HTML interno es del app — aquí la regla B-2 no aplica (es la excepción documentada). - Demo: documento markdown real renderizado (headings, listas anidadas, tabla, código, blockquote, callout incrustado), claro/oscuro, densidades.
F1.6 anchor-nav — índice con scrollspy
- Gap: TOC lateral con sección activa según scroll (docs, settings largos, landing largas).
- Membresía: soma + eidos (observación + estado activo + navegación).
- Fase 0, comparar: AntD
Anchor· MantineTableOfContents· Starlight/Docusaurus TOC. APG: no hay patrón de widget — es unnavlandmark conaria-current; anotarlo en el README (válvula A-1.4 con nota, como pagination/stepper). - Morfo (boceto): parts
provider(nav,aria-labelvíatexts:— aquí SÍ hay string de chrome: "On this page"/"En esta página" → catálogo langs) +list+item+link(¿archetype de item interactivo? — validar contra vocabularies; el link componeLinkdel catálogo). Data:data-activeen item;aria-current. Eventos: click de link = navegación (familia/verbo a decidir en fase 1 contraSEMA_VERBS— candidato familiashift; validar).expression:según criterio D.4. - Soma: registro de secciones objetivo (por id o por
attachPart), observación víadom.observe(IntersectionObserver, umbrales al gusto del provider) — mismas reglas anti-reflow que F1.1. El activo es estado del provider; los effects estampandata-active. Scroll programático al click víadom(scrollIntoView por ActiveDom), no window crudo. - Eidos: raíl vertical con indicador de activo (¡leer la memoria NavMenu Indicator: soma posiciona, eidos da forma, nunca transition de transform!); niveles h2/h3 con indentación tokenizada.
- Demo: página larga real con
prose(F1.5) + anchor-nav vivo.
F1.7 sidebar — navegación vertical de app
✅ E-1 FIRMADA (2026-07-21): la colisión sidebar-app vs árbol-docs (P5/B-5) se resuelve con el componente canónico
nav-tree(F1.8) — este sidebar queda app-céntrico. Además: el dossier (P4) fija el suelo shadcn (23 partes, dos ejes estado/modo, costuras open/onOpenChange/ toggle expuestas desde v1) — la fase 0 dimensiona contra él por E-2.
- Membresía: soma + eidos. Es el componente más pesado de F1 — reservarle tanda propia.
- Fase 0, comparar: shadcn
Sidebar(el patrón de referencia actual) · MantineAppShell.Navbar· Ark/Radix (no lo tienen — anotar). Scope al usuario ANTES de construir: qué features de shadcn entran en v1 (propuesta v1: grupos + colapsable a raíl con tooltips + activo + slots header/footer; FUERA v1: keyboard shortcut global, persistencia — llega del app por D-BLK.6, submenús flotantes en raíl). - Morfo (boceto): parts
provider+header+content+group+group-label+item+footer+trigger(botón colapso — componeButtonconasChild/child pattern). Data:data-collapsed,data-rail,data-active(item). Eventos: toggle de colapso (verbo candidato del set real; evaluación neutral), activación de item si el item es más que unLink— decidir en fase 1 (si item =Linkpuro, la navegación no necesita evento propio del sidebar). - Soma: estado collapsed/rail; grupos colapsables PUEDEN componer el
provider de
collapsible(precedente compose-compound-in-component: NumberField dentro de Knob, con aislamiento de eventos); variante móvil COMPONEDrawer(no lo reimplementa) — el provider decide qué montar por breakpoint responsive del sistema, no matchMedia propio. - Eidos: raíl con
will-changecuidado (memoria:will-change: transformproduce jitter en raíles finos con DPR≠1 — override aauto); tooltips de item en modo raíl componenTooltip; tokens--sidebar-*(width, rail-width, paddings). - Demo: shell de app simulada, toggle colapso, grupos, móvil (375px) con drawer, RTL.
F1.8 nav-tree — árbol de navegación data-driven (E-1, 2026-07-21)
- Gap: navegación jerárquica profunda (docs-shell, árboles de páginas) con active-trail — lo que el dossier (P5) demostró que NO cabe en el sidebar-app: ≥3 niveles, active-trail auto-expandido, auto-colapso de hermanos, profundidad default-open, badges, scroll-active-into-view, presentación móvil. Data-driven por diseño (el app pasa el árbol como datos — precedente sancionado: Menubar; B-5 no se viola porque el componente canónico ES data-driven).
- Membresía — fase 0 OBLIGADA contra nuestro propio catálogo: leer
tree-view(ya existe: selección de nodos) ANTES de diseñar y delimitar la frontera tree-view (selección/widget tree) vs nav-tree (navegación por links, landmark nav) — o justificar extender tree-view. La decisión es del scope-approval. - Fase 0, comparar: sidebars de Starlight/Docusaurus/Fumadocs (dossier
P5 — comportamientos y anatomía) + APG Disclosure Navigation (dossier
P4/P3: botones
aria-expanded/aria-controls, Esc devuelve foco, flechas opcionales) +tree-viewpropio. - Morfo (boceto): parts
provider(nav landmark,aria-labelvíatexts:) +list+item+trigger(grupo colapsable) +link(componeLink;aria-current="page"del MISMO estado quedata-active)- slot badge (compone
Badge). Data:data-active,data-expanded, profundidad como CSS var tokenizada (no attr por nivel). Keyboard: patrón disclosure; roving opcional opt-in.
- slot badge (compone
- Soma: árbol como datos; el activo llega por costura del app (matcher
de URL — el componente NO conoce el router); active-trail expande
ancestros; colapso de grupos evalúa componer
collapsible(compose-first); scroll-active-into-view víadom. - Eidos: indent por nivel con token (
--nav-tree-depth-offset, estilo Mantine pero tokenizado), línea/raíl de nivel, estados active/expanded. - Demo: árbol real de ~40 nodos y 3 niveles (p. ej. el propio mapa de docs), active-trail vivo, móvil 375px.
Cierre F1: los 8 con component:audit --only PASS (o NEEDS-WORK
únicamente por reglas D-* de demos v3 si esa fase global sigue abierta —
anotar en la tabla), morfo:check, eidos-lint por componente, suite
npx vitest run src/uix/eidos verde, npm run check sin regresión sobre
baseline.
F2 — Blocks de sitio (14)
Todos entran por el contrato B. Ficha = Función · Compone · API (partes) · Layout/landmark · v1 · Demo. Regla transversal: fase 0 ligera SIEMPRE (mirar el block equivalente en ≥2 catálogos de referencia de §4 y anotar en el README qué se adopta/descarta). Variantes: v1 = LA variante (una); ampliaciones = Gaps con disposición, no código especulativo.
Depende de: F1.1 (sticky) para F2.1; el resto de F2 no depende de F1.
F2.1 site-header
- Función: cabecera de sitio con afijado y cambio de elevación al pegarse; colapso a menú móvil.
- Compone:
Sticky(F1.1) +Container+NavigationMenu+Button+Drawer(móvil) +Link+Separator. - API:
<SiteHeader>(props:sticky?,container?) +.Brand+.Nav+.Actions+.MobileNav(children del drawer). - Layout/landmark:
<header>+<nav aria-label>; skip-link como primer foco (decidir en su fase 0 si el skip-link vive aquí o en los shells — una sola respuesta, documentada). - v1: brand izquierda · nav centro · actions derecha · drawer móvil;
estilización del estado pegado vía
data-stuck(elevación/fondo con tokens de los componentes compuestos, no CSS nuevo). - Demo: página con scroll largo, claro/oscuro, 375/1280, RTL.
F2.2 hero
- Función: sección de apertura con titular, subtítulo, acciones y media.
- Compone:
Section+Container+Stack/Grid+Heading(nivel configurable, default h1) +Text+Group(acciones conButton) +Badge(chip anuncio) +Image/AspectRatio. - API:
<Hero>(proplayout: 'center' | 'split') +.Eyebrow+.Title+.Description+.Actions+.Media. - Layout/landmark:
<section aria-labelledby={title.id}>; un solo h1 por página es responsabilidad del app — el README lo dice. - v1:
centerysplit(la segunda existe porque discrimina el layout, no por lujo — es el criterio "casos que discriminen"). - Demo: ambos layouts, con/sin media, con badge, dark.
F2.3 feature-grid
- Compone:
Section+Container+AutoGrid+Stack+Icon+Heading+Text. - API:
<FeatureGrid>+.Header(title+description de sección) +.Item(con.ItemIcon/.ItemTitle/.ItemTexto children libres). - v1: items planos (sin Card — la variante card es Gap).
- Demo: 3/6 items, columnas responsive del AutoGrid.
F2.4 pricing
- Compone:
Section+CardGroup/Card+Heading+Text+Badge(plan destacado) +Button+ToggleGroup(mensual/anual) + filas de features (Stack+Group+Iconcheck +Text). - API:
<Pricing>+.Switch(billing toggle; estado de vista local permitido §1) +.Plan(propfeatured?) +.PlanPrice+.PlanFeatures.PlanAction.
- v1: 2–4 planes en fila responsive; el precio mostrado por periodo lo
resuelve el app con el valor del toggle (block emite el cambio vía prop
callback del ToggleGroup — sin formatear moneda: eso es
FormatNumberdel app). - Demo: 3 planes, featured al centro, toggle vivo.
F2.5 testimonials
- Compone:
Section+AutoGrid+Card+Avatar+Text+Group. - API:
<Testimonials>+.Header+.Item(+.ItemAuthorcon Avatar/nombre/cargo). - v1: grid; variante
Carousel= Gap (el componente existe; entra cuando una demo real la pida).
F2.6 faq
- Compone:
Section+Container(medida estrecha) +Heading+Accordion. - API:
<Faq>+.Header+.Item(proxy fino de Accordion.Item con children pregunta/respuesta — respetando el API real del Accordion, que se lee antes). - v1: una columna;
typedel accordion expuesto tal cual.
F2.7 stats-band
- Compone:
Section+Group/AutoGrid+Metrics+CountUp+Text. - API:
<StatsBand>+.Stat(valor + etiqueta;CountUpopt-in por prop). - v1: banda horizontal 2–4 stats; los formatos numéricos llegan ya
formateados o vía
FormatNumbercompuesto por el app.
F2.8 cta — HECHO (2026-07-30)
- Compone:
Section+Container+Motion+Surface(tratamiento de fondo del sistema — un CTA se distingue por acabado, y eso ya es vocabulario del framework) +Heading+Text+Flex(Buttons). - API:
<Cta layout color gradient level container size>+ slots de snippeteyebrow·title·description·actions·children. Desviación del plan: no.Title/.Description/.Actions— las partes no repiten ni coordinan, así que son slots posicionales (misma forma quehero).variantNO existe: medido, el canvassoftno acota panel (bitácora).
F2.9 newsletter — HECHO (2026-07-30)
- Compone: como F2.8 +
Form(variant="plain") +Gridde fila1fr auto; elFieldde correo y elForm.Submitlos compone el app en sus slots. - API:
<Newsletter layout panel color gradient level container size>+form(OBLIGATORIO, decreateForm) +schema(opcional) + slotseyebrow·title·description·field·submit·note·children. Desviación del plan: slots, no.Title/.Description/.Form(las partes no repiten ni coordinan). No exponeonValidSubmit:Form.Providerlo ignora cuando recibe unformya construido, así que el handler va en elcreateFormdel app. - Nota: separado de
ctaporque el form introduce a11y y estados (invalid/submitting) que el CTA puro no tiene. - El block no inventa validación:
Formposee runtime, esquema, dirty/ touched, agregación de errores y foco al primer error;Fieldposee el cableado ARIA. Tampoco emite sema:commit-submit/signal-invalidya los declara el morfo delForm.
F2.10 site-footer — HECHO (2026-07-30)
- Compone:
<footer>+Section+Container+Motion+Grid(1fr 3fr: marca | columnas) +AutoGrid(las columnas) +Separator+Flex(barra inferior). - API:
<SiteFooter container size minColumnWidth>+ slotsbrand·signup·social·legal·extra+ partes compound.Columny.ColumnTitle. Desviación del plan:.Social/.Legal/.Extrason SLOTS, no partes — no se repiten ni coordinan;.Columnsí se repite, así que es compound.extraes el slot libre de D-BLK.6. - v1: 2–5 columnas fluidas (
minChildWidth, sin breakpoints) → 2 en móvil.
F2.11 banner — HECHO (2026-07-31)
- Compone: el componente
Bannerdel canon (reenviando su superficie entera víaOmit<BannerProps, 'children'>) +Container+Wrap+Banner.Close. - API:
<SiteBanner container onDismiss>+ slotsbadge·children·action. El dismiss lo posee elBanner(composición, no booleano), así que el block solo cablea el clic y la visibilidad es del app. - Nota dossier (P1): 13 TW · 16 Untitled · 5 Flowbite — la categoría convergente más barata de cubrir (el componente ya existe).
F2.12 team — HECHO (2026-07-31)
- Compone:
Section+Container+AutoGrid+Stack+Heading+Text+Group+Motion. ElAvatarlo compone el APP dentro delMember. - API:
<Team align container size>+.Header+.Members+.Member+.MemberName+.MemberRole+.MemberLinks. Desviación del plan: NO hay.MemberAvatar— no tendría nada que añadir sobre<Avatar size radius>y escondería su API (Image/Fallback/estados de carga). Se envuelve lo que el block DEFAULTEA, no lo que solo reenvía.
F2.13 contact — HECHO (2026-07-31) · el primer block que COORDINA
- Compone:
Section+Container+Grid+Form+Field+TextArea+Stack+Group+Motion; las filas de contacto las llena el app (Icon+Text+Link). - API:
<Contact container size form? schema? verification? bind:sent onSend>+.Header+.Body+.Form+.Fields+.Submit+.Reason+.Details+.Detail+.DetailLabel+.DetailValue. - Desviación del plan: el plan dibujaba
.Info+.Formy dejaba «validación y submit = handlers del app». Se cumple lo segundo (el envío sigue siendo del app poronSend), pero el reparto cambió con la doctrina de coordinación: el block posee el ESTADO de la sección, la FORMA de sus datos y las PALABRAS de cada estado..Infose partió en.Details/.Detailporque la fila se repite, y aparecieron.Fields,.Submity.Reasonporque coordinan (leen el estado por contexto). - La máquina (
state.ts):incomplete · unverified · verifying · rejected · ready · sending · sent, conCONTACT_REASONyCONTACT_ACTIONcomoRecordexhaustivos — un estado bloqueado sin frase no se puede escribir. Cubierta porstate.test.ts(9 casos), primera lógica pura del tier y por tanto su primer test unitario. - Defecto encontrado al verificar: la máquina leía
form.isValid, que conprogressivesignifica «aún no se ha encontrado nada mal» — un formulario vacío se declaraba válido yincompleteera INALCANZABLE al cargar: la sección pedía resolver la verificación con los tres campos vacíos. Arreglado preguntando al esquema (validateSyncde SIUM: síncrono y sin escribir errores en el formulario). - i18n: fallbacks ingleses en el block; el arnés registra el namespace
blocks(web/routes/blocks/_lib/blocks-langs.ts) en las DOS cadenas de arranque (galería y previews) y la demo se lee en castellano. - Contrato: la doctrina está escrita en
architecture/blocks.md§«Coordination» + B-5/B-7 enmendados + la convención de servicios (el traductor es el único servicio sancionado).
F2.14 content-section — HECHO (2026-07-31) · cierra F2
- Compone:
Section+Grid+Box+Prose(F1.5) +Heading+Text+Motion. - API:
<ContentSection measure wide size level>+ snippets de cabecera (eyebrow/title/lede/meta) +.Body+.Media. - Desviación del plan — SIN
Container: el plan decíaSection+Container+Prose, pero dentro de unContainernada puede ser más ancho que él, así que la propia meta del plan (media FUERA del flujo del prose) sería imposible: una figura a sangre habría que sacarla del artículo y ponerla de hermana, rompiendo el orden de lectura. La sección es una REJILLA de 5 pistas cuya central es la medida, y cada parte dice hasta dónde llega (measurecol 3 ·widecol 2/5 ·fullcol 1/-1). Ese escape es lo único que este block añade sobre un<Prose>en una caja — sin él sería un envoltorio de tres capas. - Todas las longitudes son tokens: medidas
--measure-*(54/66/78ch), pista ancha--container-width-*, canalones con suelo en--container-padding-inline. - Semántica propia: renderiza
<figure>/<figcaption>de verdad (no son interactivos → estructura de documento, B-8, no la regla de admisión). Hueco señalado: el canon no tiene primitivaFigure. - Contexto de CONFIGURACIÓN (no estado): la raíz comparte la medida para
que el pie de una figura desbordada se alinee con la columna de lectura —
mismo uso que el
aligndeteam. Es un block de LAYOUT y no coordina nada, como dicearchitecture/blocks.md§«Coordination».
(logo-cloud sigue en F5: su versión honesta pide Marquee o queda en un
Wrap trivial que no justifica block todavía — decisión anotada, revisable.
El resto de la unión del dossier — bento, gallery, cookie-consent, popups,
careers, events, comparison, timeline — queda en F5 con disparador, E-3.)
Cierre F2: blocks-check verde; galería web/routes/blocks/ con los 14;
una página compuesta de PRUEBA (header + hero + features + pricing + faq +
cta + footer juntos) que valide que los blocks ensamblan sin fricción — esa
página ES el test de integración del tier y se mira en claro/oscuro/375/1280.
F3 — Blocks de aplicación (10)
Mismas reglas y ficha que F2. Depende de: F1.2/F1.3/F1.4 (estados),
F1.7 (sidebar) para F3.1.
F3.1 app-shell
- Función: esqueleto de aplicación: sidebar + topbar + contenido (+ aside opcional).
- Compone:
Sidebar(F1.7) +Sticky+Container/Grid+ScrollArea+Group+ slots. - API:
<AppShell>+.Sidebar+.Topbar+.Content+.Aside. - Landmark:
header/nav/main/aside+ skip-link (según decisión F2.1); jerarquía documentada. - v1: sidebar colapsable + topbar afijada + main con scroll propio; móvil = sidebar en drawer (lo trae F1.7).
- Excepción B-10 declarada: los shells componen otros elementos de F1 y
blocks — allowlist en
blocks-check.
F3.2 auth
- Función: familia de formularios de identidad. Sub-blocks:
sign-in·sign-up·recover·otp. - Compone:
Card+Form+Field+PasswordField+PinInput(otp) +ProofOfHuman+Button+Separator+Link+ slot de proveedores sociales (Buttons del app). - API:
<AuthCard kind='sign-in'|…>con partes (.Title·.Fields·.Actions·.Footer), o cuatro exports finos — decidir en su fase 0 mirando el API real deForm(lo que menos indirection genere gana: no-premature-abstraction). - Regla dura: TODO handler (submit, oauth, resend) llega del app; el
block no conoce transporte ni credenciales. La validación es del sistema
Formcon schemas del app. - Demo: los 4 kinds, estados invalid/submitting, claro/oscuro.
F3.3 data-table
- Función: la tabla de trabajo completa: toolbar (búsqueda + filtros +
visibilidad de columnas + bulk actions con selección) +
Table+Pagination+EmptyStateintegrado. - Compone:
Table+SearchField+DropdownMenu+Button+Pagination+EmptyState(F1.2) +Group/Toolbar. - ⚠️ Fase 0 OBLIGADA y más profunda: leer
soma/components/table+$libs/datagridANTES de diseñar el API — el block debe montar sobre las capacidades reales (sorting/selection/paging que ya existan). Cada capacidad que falte = FLAG con disposición (candidata a canon), nunca lógica de datos dentro del block. El estado de datos es del app; el block orquesta la UI. - API:
<DataTable>+.Toolbar(+.Search/.Filters/.Columns) +.Content(la Table compuesta por el app con su API real) +.Selection(barra bulk) +.Footer(Pagination) +.Empty. - Demo: dataset realista (≥100 filas), búsqueda+filtro+selección+bulk, estado vacío tras filtrar.
F3.4 dashboard
- Compone:
AutoGrid/Grid+Card+Metrics/CountUp+Chart(tipos ya existentes) +Feed+EmptyState. - API:
<Dashboard>+.Stat+.Panel(card con.PanelHeader+ children libres para chart/feed). - v1: composición fija de ejemplo con slots; nada de layout persistible
ni drag (Gap explícito; si un día entra, el drag es de
drag-drop).
F3.5 settings
- Compone:
Container(medida estrecha) +Section+Heading+Separator+ filasField/controles +Callout(F1.4, intentthreat)AlertDialog(confirmación destructiva) +Button.
- API:
<Settings>+.Section(+.SectionTitle/.SectionDescription).Row(label+control+ayuda) +.DangerZone.
- Regla: cada control es el componente del ecosistema (nunca nativos —
B-2); el guardado (por fila o global) lo decide el app vía
Form.
F3.6 user-menu
- Compone:
Avatar+DropdownMenu(+ items conIcon,Separator,Kbdopcional). - API:
<UserMenu>+.Trigger(Avatar + nombre) +.Items(children = items del DropdownMenu real). - Nota D-BLK.6: tema/idioma/logout = items que el APP cablea; el block da la estructura y los puntos de montaje.
F3.7 notifications
- Compone:
Popover(desktop) /Drawer(móvil) +Badge(contador en el triggerIconButton) +Feed+EmptyState+Button(«marcar leídas» — string del app). - API:
<Notifications>+.Trigger+.Panel(+.Header/.List/.Empty/.Footer). - Trampa conocida: panel más ancho que el min-width del Popover →
recorte; pasar
widthpor el prop tokenizado delPopoverContent(la lección ya está aprendida — no re-descubrirla).
F3.8 wizard
- Compone:
Stepper+Form+GroupdeButtons (atrás/siguiente/ finalizar) +Progressopcional. - API:
<Wizard>+.Steps(Stepper compuesto) +.Step(contenido) +.Nav. - Fase 0: leer el API real de
Steppery deForm(validación por paso) — el block solo coordina visibilidad de paso + navegación; el estado de validez lo dictaForm.
F3.9 error-page
- Compone:
Result(F1.3) +GroupdeButton/Link+SearchFieldopcional (404). - API:
<ErrorPage status=…>+ children de acciones. - v1: 404 · 403 · 500 · offline.
F3.10 kanban
- Compone:
DragDrop+Grid/Groupde columnas +Card+VirtualList(columnas largas) +Badge+EmptyStatepor columna. - ⚠️ Fase 0 OBLIGADA: leer
soma/components/drag-dropa fondo; el block mapea sus primitivas reales (zonas, handles, anuncios a11y del provider). Toda carencia = FLAG (candidata a canon), no workaround. - API:
<Kanban>+.Column(+.ColumnHeader) +.Card(children libres). El modelo de datos y la persistencia del orden son del app. - Demo: 3 columnas, drag entre columnas, columna vacía, teclado.
_(Diferidos de F3, registrados en F5: scheduler — bloqueado hasta que
chronos aterrice; chat-room — NO va aquí: la familia chat-_es canon y
su roadmap vive ennext-features.md §7.)*
Cierre F3: blocks-check verde; demo de app-shell montando dentro
data-table + dashboard + notifications + user-menu como página de
integración; navegador claro/oscuro/375/1280 + teclado (tab por toda la
página de integración sin trampas de foco).
F4 — Blocks de docs (3)
Depende de: F1 completo (prose, anchor-nav, nav-tree, callout) + F2.1. Conecta con la iniciativa docs-corpus=site (el corpus es la semilla del sitio); estos blocks son su vehículo de UI.
F4.1 docs-shell
- Compone:
AppShell(F3.1, excepción B-10) especializado:NavTree(F1.8) como árbol de docs +Prose(main) +AnchorNav(TOC derecha) +Breadcrumb+ trigger de búsqueda (Command— ya existe) + prev/next (Groupde dosCard/Link). - E-5 firmada: el atajo ⌘K del trigger de búsqueda = listener app-land
documentado en el README del block; el art
shortcutsconserva su disparador F5 (≥2 consumidores reales). - API:
<DocsShell>+.Nav+.Article(prose) +.Toc+.Search.PrevNext.
- v1: 3 columnas desktop → TOC colapsada y sidebar-drawer en móvil.
F4.2 code-showcase
- Compone:
Tabs(Preview/Code) +Surface(lienzo de preview) +CodeBlock+Clipboard+Toolbaropcional. - API:
<CodeShowcase>+.Preview(children vivos) +.Code(children CodeBlock). - v1: preview + código + copiar. (Chips de tema/dirección/densidad del lienzo = Gap, notado — pediría servicios, D-BLK.6.)
F4.3 props-table
- Función: tabla de referencia de un componente generada del morfo —
parts, data-attrs, ARIA, keyboard y eventos salen de
compileMorfo(ventaja estructural única de este framework: el contrato ES introspectable). - Compone:
Table+Code/Kbd+Badge. - API:
<PropsTable morfo={…}>(aquí el input data-driven es legítimo: el "árbol" es el contrato canónico, no contenido inventado). - v1: tablas morfo-backed (parts/attrs/keyboard/eventos con familia·verbo·intent). Tabla de props TS = Gap tracked (requiere extracción de tipos; no improvisar — flag para decidir tooling).
Cierre F4: una página de docs REAL montada con los 3 (un doc del corpus
renderizado en docs-shell con showcase y props-table de un componente
PASS), verificada en navegador.
F5 — Backlog condicionado (no construir sin disparador)
Componentes canon candidatos (cada uno entraría por la ruta de 9 fases):
| Pieza | Disparador |
|---|---|
lightbox (visor imagen zoom/galería) |
cuando un block de media/galería real lo pida |
tour (onboarding spotlight) |
cuando el app-shell tenga consumidor real con onboarding |
hover-card genérico |
cuando un tercer caso no-URL aparezca (hoy link-preview cubre) |
description-list |
primer detail-view real (pareja natural de data-table) |
loading-overlay |
primera pantalla con carga bloqueante real |
transfer-list |
primer admin real con asignación dual |
mention |
ya registrado en next-features.md §7 (chat) — no duplicar aquí |
masonry · bottom-nav · swipe-actions · pull-to-refresh · image-compare · marquee (¿pack?) · watermark · signature-pad |
demanda real; varios son candidatos a pack, no a canon — decidir con la regla de admisión de packs |
art shortcuts (registro global de atajos + cheat-sheet con Kbd) |
cuando ≥2 consumidores reales (command palette global + docs) lo pidan |
Blocks diferidos: scheduler (bloqueado por chronos) · logo-cloud
(bloqueado por decisión marquee) · billing · file-manager ·
profile-card (cuando haya consumidor).
6. Verificación de cierre del plan (definition of done global)
npm run checksin regresión;npm run blocks:checkverde;npm run component:audit→ los 7 de F1 PASS (o NEEDS-WORK solo por D-* de la fase demos global);docs:checkverde.- Las tres páginas de integración (F2 landing · F3 app · F4 docs) renderizan compuestas SOLO de canon+blocks, verificadas visualmente en claro/oscuro, 375/1280 y teclado.
- Doctrina publicada y enlazada (architecture/blocks.md en el mapa; CLAUDE.md al día); cada block con README B-9 completo.
- Ningún gap silenciado: todo ❌/⚠️ de las fases 0 tiene decisión firmada o fila en F5/next-features.
7. Registro
-
2026-07-21 — Plan creado (análisis de ecosistema + decisión de usuario: tier
blocks). Iniciativa ennext-features.md§8. -
2026-07-21 — F0.1 HECHA: las 7 D-BLK firmadas (repaso en sesión). D-BLK.1 enmendada frente a la propuesta: el tier vive en
src/uix/blocks/(nosrc/blocks/); referencias del plan actualizadas (tabla de tiers, B-4, blocks-check, F0.3/F0.4). Resto firmado como propuesto. Siguiente: F0.2 (doctrina enarchitecture/blocks.md). -
2026-07-21 — F0 CERRADA (misma sesión): doctrina
docs/architecture/blocks.md· alias$blocks(vite + svelte.config + CLAUDE.md) ·src/uix/blocks/README.md(mapa + template B-9) ·scripts/blocks-check.ts+npm run blocks:check(self-testing) · galeríaweb/routes/blocks/con layout de bootstrap propio (+layout@.svelte, espejo mínimo del de/uix, sin packs sema) · fila E1 endocs/README.md. Nota de commit:docs/README.mdydocs/next-features.mdquedaron FUERA del commit de F0 — ambos traían diff foráneo de otra sesión imposible de separar por staging de archivo completo; sus hunks propios (fila E1 + §8) viajan con el árbol de trabajo hasta que su dueño commitee. Siguiente: F1 (orden recomendado:empty-state→result→callout→sticky→prose→anchor-nav→sidebar). -
2026-07-21 — Estudio de referencia COMPLETO (encargo del usuario: "a la par y cuanto menos superarlo"): 6 pistas de investigación web en paralelo →
docs/process/RESEARCH-blocks-references.md(suelos de paridad a nivel de prop por ítem, pitfalls, superación). Regla nueva en §4: toda fase 0 contrasta contra el dossier. Puntero de colisión añadido a F1.7. Enmiendas E-1…E-5 presentadas al usuario (E-1 nav-tree · E-2 filosofía de alcance v1 · E-3 categorías nuevas F2 · E-4 registry/llms/MCP · E-5 ⌘K) — firmas pendientes; se registran aquí. -
2026-07-21 — E-1…E-5 FIRMADAS (todas según recomendación): E-1 componente canónico
nav-treedata-driven (F1.8; F1 pasa de 7 a 8; el naming se valida en su fase 0 como extensión de D-BLK.7); E-2 suelo de paridad = v1 (regla en el intro de F1); E-3 F2 pasa de 10 a 14 (banner · team · contact · content-section; resto de la unión a F5); E-4 distribución registry/llms.txt/MCP registrada como iniciativa propia ennext-features.md§9; E-5 ⌘K = listener app-land (nota en F4.1). Doctrina (architecture/blocks.mdpromotion path) y galería actualizadas. -
2026-07-21 — F1.2
empty-stateHECHA (ruta de 9 fases completa, suelo E-2 del dossier §P3): morfo display 5 partes (scope: ['eidos'], 0 eventos justificados, Titlerole:'heading') + langslabel+ eidos compound (Media kind=icon|mediacon placa 2× glifo ·Title level2–6 default h3, modelo Atlaskit ·Descriptionmeasure 45ch ·Actions label→ role=group, buttonGroupLabel) + recipe sobre el bundle--size-*(título un paso discreto arriba) + demo v2 de 9 tabs + README (Comparativa 5 refs · Passive justification · Gaps con disposición). Verificado:component:auditPASS · eidos-lint 5 morfo-backed + 4 eidos-only sancionados ·svelte-check76E/51W = baseline exacto · navegador claro Y oscuro por estilos computados (título 18→24px, placa 40→64px, chips vivos). Siguiente: F1.3result(comparte esqueleto; fase 0 contra dossier §P3). -
2026-07-21 — F1.3
resultHECHA (ruta completa, suelo E-2 §P3): morfo 6 partes (extrasin archetype — parte genuinamente propia) · status enum de 7 CONwarning(decisión del dossier firme), defaultinfo, HTTP renombrados semánticos y renderizados NEUTROS (código mono grande, jamás rojo — doctrina AntD) · media default compone el mapa doctrinalIntentIcon(fulfill/threat/risk) +Infode catálogo, children lo reemplazan entero (context local eidos-only con getter reactivo) · Title default h2 (vs h3 de empty-state — deliberado) · esqueleto duplicado conscientemente (README lo registra; revisable al 3er consumidor) · sin eje size (paridad AntD). Verificado:component:auditPASS 0E/0W · eidos-lint 6 morfo-backed + 5 eidos-only · guards recipe 37/37 · navegador: success=fulfill verde 84px · error=threat rojo · not-found=«404» mono NEUTRO aria-hidden, copy conmutando, cero errores consola. Siguiente: F1.4callout(decisión IMPORTANT/5º hueco + role=note; dossier §P3). -
2026-07-21 — F1.4
calloutHECHA (ruta completa, suelo E-2 §P3): morfo 4 partes +textscon títulos default localizados por intent (note/tip/warning/caution — el patrón GitHub de label visible; la semántica nunca viaja solo en color) · el hueco IMPORTANT resuelto con el modelo Radix:intent(4, mapea el vocabulario de facto) +coloroverride SOLO bajo neutral (doctrina §4 — intent evaluativo gana), montado sobre la maquinaria C6/THM-2 de badge (forwarders +_palette-*→ 8 roles + 33 escalas + custom con CERO CSS extra; el forward presence-guarded se emitió solo) ·role="note"nombrado por el Title víaaria-labelledbySOLO mientras está montado (registro reactivo por context; bug cazado en navegador: el+=del registro leía el estado dentro del tracking del$effectdel hijo → effect_update_depth y contador a −997 — fixuntrack, lección para la memoria) · Title NO heading (protege el outline de anchor-nav) · icono = mapa doctrinalIntentIcon· escalación tipadarole: status|alert|none(elrole="alert"estático de shadcn NO se copia) · dismissible DIFERIDO a pasada soma+sema (regla de admisión). Verificado: audit PASS 0E/0W · lint 8 morfo-backed/0 invalid · guards 37/37 · navegador: «Nota»/« Atención» localizados, risk=ámbar hue 45-60, IMPORTANT=plum hue 326 por shared layer, labelledby=titleId. Siguiente: F1.1sticky(orden del plan: desbloquea F2; dossier §P3 — offsets, scroll-host, centinela por borde, espejo scroll-state). -
2026-07-21 — F1.1
stickyHECHA (commit998b11d9b, PASS 0E/0W). El primer componente BEHAVIORAL (soma+eidos). Precedido de un fan-out de investigación (workflow, 6 lectores) que fijó el mapa de construcción: template = FeedSentinelProvider; motor =$adom.observeIntersection(nunca scroll+getBoundingClientRect); escritura de attrs porsyncAttrs(nuncadom.applycrudo). Diseño: un StickyProvider registra AMBAS partes (box+sentinel) en un runtime;$effectretorna el observer como teardown; callback async volteastuck=$state; rootMargin DERIVADO del offset (la línea de disparo del centinela = la línea de pin — acoplamiento matemático); centinela = HERMANO de flujo del box (dos nodos raíz), no hijo; offset viaja como--_sticky-offset(dato A8); recipe SOLO posiciona (el consumidor decora[data-sticky][data-stuck]);data-stuck/data-edgeespejan@container scroll-state(stuck: top/bottom)(polyfill del contrato CSS futuro). Verificación clave: 5 tests, incl. 1 real-IntersectionObserver en chromium foreground (no-stuck→scroll→stuck→back, prueba end-to-end de la geometría — lo que el Browser pane suspendido NO puede). Decisión declarada: el test usa elinstallSomaHarnesscompartido (los 86 tests lo hacen) — en tensión con la regla CLAUDE.md «never fake translators»; flaggeada al usuario para ratificar. Notas de commit: nav sidebar fuera (flip CRLF ajeno de 2125 líneas); índices langs/soma reconstruidos sticky-only (entangle conaurasin commitear de sesión paralela). Consola: detectado un warning reactivo residual encallout(registerTitle en $effect) — a investigar. Siguiente: F1.5prose(o F1.6 anchor-nav / F1.8 nav-tree; los 3 restantes que quedan de F1 tras prose son anchor-nav, nav-tree, sidebar). -
2026-07-21 — Review adversarial de sticky (workflow
wrssrn7tx, 3 lentes + verify): 1 hallazgo CONFIRMADO (minor) — pin en eje lógico vs observer físico, diverge en writing-modes verticales. Corregido (6cba9a0b1): recipe atop/bottomfísico + margins físicos; 3 claims «logical/RTL» del demo corregidos. Re-verificado PASS 0E/0W + test real-IO. (1 lente teardown-ssr falló por API stall; caminos cubiertos por tests.) F1.1 sticky CERRADO. -
2026-07-21 — F1.5
proseHECHA (commit5b84749ee, PASS 0E/0W). Quinto F1 (eidos-only). EL DIFERENCIADOR verificado en navegador: reglas de elemento a especificidad CERO (:where([data-prose] EL)) → un<Callout>embebido conserva su tinte/borde/grid AUTOMÁTICAMENTE (su[data-callout]gana a:where), en claro Y oscuro — sinnot-prose, cosa que ninguna ref puede. Escala EM-relativa (un font-size raíz re-deriva todo, no la escala 5× de tailwind); overflow de tabla estilo GitHub; dark gratis por roles (no-invert); propiedades lógicas. Decisión:where(no@scope, diferido a Gaps). Lección: recipe token defs (base.ts) admiten literales sin tripar R-2.7; los literales em en el .css se justifican con/* literal */(21 añadidos). F1 = 5/8. Restan: anchor-nav (F1.6) · nav-tree (F1.8) · sidebar (F1.7). -
2026-07-22 — F1.6
anchor-navHECHA (commitfe2d43b7a, PASS 0E/0W). Sexto F1, segundo BEHAVIORAL (soma+eidos) tras sticky. TOC scrollspy: landmark<nav>+ anchors nativos; detección por IntersectionObserver de banda (rootMargin -{topOffset}px 0px -70% 0px), NUNCA scroll-listener +getBoundingClientRect(doctrina anti-reflow — Mantine/Docusaurus lo violan).aria-current="location"(spec-preciso; solo 1 de 5 refs pone aria-current). Rail por-link con acento endata-active; indent pordata-level. Verificación: test real-IO chromium (anchor-nav-io) — banda + zona muerta al fondo + fallback inicial; el pane suspendido daba ungetComputedStylede color OBSOLETO (falsa alarma, la regla[data-active]sí aplica — elfont-weight:500lo probaba). Review adversarial (4 dimensiones × verificador escéptico, 9/16 confirmadas): arregladas → ref-count en register/unregister (hrefs duplicados / churn ya no tiran un target vivo, +test); transición youtline-offsettokenizados (--duration-fast/--ease-default,calc(--focus-ring-width * -1)). Documentadas como límite v1 (Gaps) → salto instantáneo al fondo (Ctrl+Fin) en zona muerta + orden-enlaces==orden-secciones (ambas piden cambio de contrato de observación v2).data-levelfuera de rango degrada a flush (no arreglado, simplicidad). Además (37e35d7a2):npm run checkglobal destapó 2 errores de tipo en F1.1 sticky (sentinelRef debía serStatenoActive; eidos types importabaStickyPropsen vez del export renombradoProviderProps) — míos, arreglados. Mis archivos suman 0 errores de check (80→73; los 73 son deuda foránea de sesiones paralelas). F1 = 6/8. Restan: nav-tree (F1.8) · sidebar (F1.7). -
2026-07-22 — F1.8
nav-treeEN CURSO (WIP commiteado, sin cerrar). Componente COMPLETO en verde en gates estáticos (audit PASS · eidos-lint 20/0 · svelte-check 0 · contracts mis-partes limpias); morfo+soma+eidos+pack de sema+4 READMEs+todos los registros escritos. Falta demo + verificación en NAVEGADOR + review adversarial + cierre. Data-driven (E-1); disclosure propio (getter de estado + eventosemerge, pack espeja collapsible); filas propias (noLink/Badgecompuestos — Badge diferido a Gap);activeHrefcomo costura. Corregido de paso: README de soma de anchor-nav faltaba (contracts lo destapó). Handoff detallado:docs/process/CONTINUE-nav-tree.md. ⚠️ 2 fallos contracts AJENOS (menubar/radio-group, sesiones paralelas). -
2026-07-22 — F1.8
nav-treeHECHA (cierre de la ruta: demo v2 de 9 tabs con el mapa real de docs —43 nodos, 3 niveles—, verificación en navegador REAL y review adversarial de 5 dimensiones × 3 verificadores escépticos, 22 hallazgos brutos → 10 confirmados, 12 rechazados). Arreglado del review: (1) el emit de sema nunca ocurría — la partegroupes el target de los eventos y se registraba SINref, así queruntime.triggerlanzabaSomaRuntimeTargetErroren silencio (lo cazó el navegador: cerodata-event-*en el grupo frente a los de collapsible); (2) el chevron se comía media fila (inline-size:100%resolvía como flex-basis → 101px de 231 en «Soma»: pulsar el hueco tras la etiqueta plegaba en vez de navegar) → toggle compacto de 24px con suelo de diana WCAG 2.5.8; (3) ciclo potencial enparentByKey(clave duplicada = nodo padre de sí mismo →trailKeysgiraba para siempre y congelaba la pestaña) → clave sufijada + aviso del logger + guarda de ciclo en el paseo; (4) colapso ahora CONTEXTUAL (recuerda elactiveKeybajo el que se hizo: cerrar la sección que lees se respeta, pero caduca al navegar DENTRO — si no, la página actual se quedaba sin fila visible y el trail auto-expandido quedaba anulado; sigue siendo query pura); (5)childya no se comía el árbol (recibechildrenademás deprops— un árbol data-driven no lo puede reautorar el consumidor); (6) nombre accesible del chevron declarado en morfo + localizado («Alternar sección {label}») en vez de duplicar el nombre del enlace; (7) paridad de snippet en la demo + token fantasma--nav-tree-rail-widthfuera del docblock. RTL completo (el glyph se dibuja con bordes lógicos: espeja solo; lo que no espeja es el GIRO → bajo[dir='rtl']las dos rotaciones se intercambian) — el Gap «dirección del chevron en RTL» queda RESUELTO, no diferido. Decisión de usuario (delegada):badgeSÍ entra en v1 (está en el suelo del dossier §P5, los 6 refs lo llevan) resuelto con snippet, no con recursión de eidos — soma declara la partebadgey renderiza el snippet recibido (sin él, el valor crudo: sigue headless), el wrapper de eidos pasa elBadgecanónico; va DENTRO del control de la fila para que su texto entre en el nombre accesible («TSC, New, enlace»).disabledtambién entra, por decisión explícita del usuario tras presentarle el criterio: el nodo conserva la fila pero pierde elhref(no hay nada que activar con Enter, clic ni «abrir en pestaña nueva» — unpointer-events:nonede CSS solo tapa el ratón) y añadearia-disabled+data-disabled; el toggle del grupo NUNCA se deshabilita (disclosure es control de vista, no destino) y un nodo sinhreflo ignora. Verificado:component:auditPASS · eidos-lint 26 morfo-backed / 0 invalid ·svelte-check0 errores en mis archivos ·vitest src/uix/eidos353/353 · navegador real (Playwright, el pane suspendido NO sirve: congela rAF y el scroll-into-view parecía roto) → trail auto-expandido, semaemerge-expand/collapseestampando engroup, teclado nativo (Tab/Enter/ Espacio), foco visible, claro+oscuro, RTL, 375px sin desbordes, 0 errores de consola. F1 = 7/8. Resta: sidebar (F1.7). -
2026-07-22 — F1.7
sidebar— fase 0 + morfo + soma (commit2bc5fe6bd). Fase 0 presentada al usuario ANTES de construir, con 4 decisiones firmadas: 17 partes (13 estructurales +menu-action·menu-badge·menu-sub·menu-sub-button), submenú FLOTANTE en modo icono ya en v1 (superación: shadcn los oculta y deja ramas inalcanzables), semaemerge-expand/collapsesobrepanel, y ritmo por fases con parada tras morfo+soma. Contrato: DOS ejes (data-state+data-collapsibleSIEMPRE estampado) +data-side+data-mobile;panel=<aside>nombrado ycontent=<nav>nombrado con header/footer FUERA (ninguna ref trae landmark);rail=<button>real en el orden de tabulación (shadcn:tabIndex=-1);aria-current="page"del MISMO prop quedata-active;menu-badgedentro del control de la fila;separator/inputNO son partes (se componenSeparatoryField). Soma: costurasopen/defaultOpen/onOpenChange/toggle()desde v1 (sin ellas la persistencia y el atajo de app-land serían rediseño posterior),mobiledel breakpoint del SISTEMA (uix.dom.isAtLeast) y no de unmatchMediapropio, submenú flotante sobrecreateFloatingShellRootcon Escape que devuelve el foco, e ids per-instancia para los dospartRefque son last-write-wins (precedentenavigation-menu). Verde:morfo:checkcarga el contrato ·component:audit --only sidebarPASS 0E/0W ·svelte-check0 errores propios ·contracts.testsolo los 2 fallos ajenos. Siguiente: eidos (raíl, anchos tokenizados, modo icono, Drawer móvil) + demo + review. -
2026-07-23 — F1.7
sidebarHECHA · F1 CERRADA (8/8). Eidos + demo + navegador + review adversarial (commits2bc5fe6bdmorfo+soma ·21bbf9f50eidos+demo ·293593307alto completo ·dcce75cc2foco del flyout ·245c22471controles de demo ·75e1fe32areview). El review (6 dimensiones × 3 verificadores, 135 agentes) dio 43 brutos → 31 confirmados, todos arreglados. Los gordos: (1)collapsible='none'dejaba el sidebar INALCANZABLE en móvil (toggle inerte + raíl oculto + drawer sin trigger) → la inercia del modo se limita al escritorio; (2) ese mismo modo hacía MENTIR adata-state/aria-expandedsobre un panel visible → el modo pinta el estado; (3) el flyout de modo icono solo cerraba desde el propio flyout → puntero, foco y Escape se resuelven ahora en elmenu-item, que contiene fila y sub, y el flag se limpia al cambiar de presentación; (4) las filas del raíl no tenían NOMBRE (un tooltip solo describe) → el texto detooltippasa también aaria-label; (5) el panel off-canvas colapsado seguía en el tab order →visibility: hiddencon la transición retrasada; (6) el signo del off-canvas era físico → en RTL barría el panel por encima de la página; (7) el flyout era la única superficie flotante sinz-index. Verificado en navegador real todo lo anterior. Gates: audit PASS 0E/0W · eidos-lint 47 morfo-backed / 0 invalid · svelte-check 0 errores propios · vitest src/uix/eidos 353/353. F1 = 8/8: la fase queda CERRADA. Siguiente: F2 (blocks de sitio, 14). -
2026-07-23 — F2.1
site-headerHECHA · primer block del tier (commits3cfc030b3block+landmark ·016658a0ccontroles ·f98c844edfix de Box ·50574806dharness ·33328df7acromo de sección ·d574982abescenario a ras ·ae7b4f8c3vista previa honesta). Composición pura:Sticky(solo sisticky, sin wrapper inútil) +Container+Group+Box(el interruptor ancho/estrecho pordisplayresponsive, B-6) +Drawer; marca, nav, acciones y navegación móvil entran como snippets del app (B-7: cero cadenas propias). Fase 0 contra el dossier §P1: adoptado el FLYOUT (la brecha de paridad) sin reimplementarlo — el app pasa unNavigationMenu. Encontrado al componer, arreglado en el canon:NavigationMenuno emitía landmark (su morfo declaradefaultElement: 'nav'y soma pintaba undiv). Hueco registrado, no falseado → CERRADO el mismo día: el CTA que NAVEGA y parece botón. Se sirve por COMPOSICIÓN, no por prop nueva: elchild(asChild) deButtonentrega ahora un snippetcontentcon el cuerpo ya decorado, así que<a href {...props}>{@render content()}</a>recibe la pintura sólida completa y conserva icono/etiqueta/spinner (antes elchildlos sustituía: por eso la flecha del sitio alpha estaba escrita a mano); y soma deja de estampartypeen un elemento que no es suyo (<a type="button">es una pista de MIME falsa) — el morfo lo declara condicional.Buttonsigue sinhrefyLinksigue poseyendo la navegación: las dos decisiones firmadas se mantienen. Doctrina en el README de eidos Button §«CTA que navega»; en uso en la demo del block. Defecto de framework destapado por la demo: las 44 variables de cascada deBoxheredaban, así que unSectionregalaba su padding a cada descendiente (la galería arrastraba ~300px de aire desde F0) →@property { inherits: false }, radio verificado sin regresiones (docs/theming/changelog.md§46). Superficie de demo del tier (doctrina nueva, B-9 ampliada): harness compartido enweb/routes/blocks/_lib/+ cromo de sección con los ejes que mueven el framework (tema, idioma, dirección, densidad) + catálogo único. El block se enseña a sangre en la página, nunca dentro de un marco con relleno ni de una caja con scroll: medido, unCarddesplazaba 21px un header conoffset: 0, y unstickydentro de un div con scroll es un comportamiento que nadie vive. Los anchos de dispositivo se sirven desde una rutapreviewpropia (iframe opt-in: en dev, dos documentos sin empaquetar agotan las conexiones del navegador). Siguiente: F2.2hero— handoff endocs/process/CONTINUE-blocks.md. -
2026-07-23 — F2.2
heroHECHA (mismo día). API por slots de snippet (no compound):eyebrow/title/description/actions/media/background+layout: 'center' | 'split' | 'background'+level/container/size. La decisión de forma se razonó con el usuario: compound se reserva para partes que COORDINAN (Accordion, Dialog); un hero son slots de layout que el root arregla → snippets. El campo entero shippea marketing como copy-paste plano; nadie aplica compound a una sección → los slots por zona SON la historia de personalización del tier. El block envuelve título/subtítulo enHeading/Text(posee eliddel landmark y el nivel; la app pone las palabras);eyebrow/actions/mediason contenido libre. Landmark:<section aria-labelledby>nombrado por el título (verificado: región con nombre accesible). Layoutbackground(cover) añadido por scope-approval del usuario: media a sangre detrás, velo--color-overlaya--opacity-scrim, textoon-solidyobject-fit: covervía<style>justificado (D-BLK.2) — las ÚNICAS reglas que el block posee, todas sobre tokens del ecosistema, cero color a mano. La demo dogfooda el sistema de color: el backdrop es unSurfacecolor="primary" gradient(finish aurora,--gradient-auroraderivado de los roles del tema). Registrado, no resuelto:Box/Surfaceflex/growno hicieron crecer un hijo flex (bars a 0-width,flex: 0 1 auto; prop sin usar en shipped) → la demo usóGrid(tracks1fr); flag para revisar el cableado de--box-flex. Gates:blocks:checkverde (2 blocks) ·svelte-checksin errores propios. Siguiente: F2.3feature-grid(primer compound del tier:.Itemrepetido — LA brecha #1 del dossier, split/alternante texto-screenshot). -
2026-07-23 — F2.3
feature-gridHECHA (mismo día). Primer block COMPOUND del tier:<FeatureGrid>+.Header+.Items+.Item+.ItemIcon/.ItemTitle/.ItemText. Aplica la regla de forma:.Itemse REPITE (el app mapea sobre N features) → gana compound; las partes no coordinan (sin contexto/estado entre ellas), la rejilla es del padre..Itemses el envoltorio honesto de la rejilla (AutoGrid) — deja la cabecera fuera sin ungrid-column: 1/-1a pelo. El block coloca (Section·Container·AutoGrid·Surface· Heading·Text), el app pone contenido (B-7).alignde sección (center/start) coloca la cabecera coherente con las columnas. Sub-componentes de bloque extienden los props del componente canon que envuelven, NOHTMLAttributes: elstyle: string|nulldel atributo HTML crudo choca con elstyle: stringdel canon (+ "union too complex") al hacer spread. ItemIcon defaultsolid: el soft-primary en claro es casi blanco (oklch 0.99) → invisible; solid da chip con glifo on-solid (la tinta de contraste la poneSurface). Demo: control de columnas (fluido/2/3/4), align, nº items, dir; 6 features con iconos del canon. Gates:blocks:checkverde (3 blocks) ·svelte-checksin errores propios. Hueco a decisión del usuario: el feature-split/alternante (texto junto a screenshot, lados alternos) — la brecha nº1 del dossier — es otra disposición (filas de 2 columnas, no rejilla de iconos): probablemente block hermanofeature-split. Presentado como scope-approval, no resuelto aquí. Siguiente: F2.4pricing. -
2026-07-23 — Primitivo canon
Mockup+ F2.3bfeature-splitHECHOS (mismo día; scope-approval del usuario: «split + primitivo de media reutilizable»). La clave del dossier: la brecha del hero/features es el TRATAMIENTO DE MEDIA, no el conteo de layouts. En vez de falsear un screenshot por demo, se cierra de raíz con un componente del canon.Mockup(src/uix/eidos/components/mockup/+ morfo eidos-only, 0-event, comoaspect-ratio): enmarca media en cromo de dispositivo —chrome: 'plain' | 'browser' | 'phone'+urlpara la barra. Recipe dibuja la barra (dots + pill de URL), el bisel del teléfono y el notch; todo sobre tokens (--radius-*,--color-border-*,--shadow-*,--primitive-*). eidos-lint: 0 invalid / 0 class-hooks (mismo patrón que aspect-ratio, todo eidos-only). Retrofiteado en la demo delhero(dogfood).feature-split(compound:.Rowrepite):<FeatureSplit>+.Row(reversed, slotmedia) +.Eyebrow/.Title/.Text/.Features/.Feature/.Actions.reversedmueve la media al inicio víagrid-column(Box tienegridColumn/order), dejando la copy PRIMERA en el DOM (orden de lectura intacto). El check de.Featurees decorativo (aria-hidden). La media se compone conMockup.- BUG de
heroarreglado de paso (lo tapaba mi filtro de svelte-check con backslashes mal escapados): los snippetstitleybackgroundcolisionaban con los atributos HTML homónimos (title?: string/background) →string & Snippet. Fix:Omit<HTMLAttributes, 'children' | 'title'>+ renombrar el slotbackground→backdrop. Lección para blocks: un slot de snippet cuyo nombre sea un atributo HTML necesita Omit o un nombre distinto. Gates:blocks:checkverde (4 blocks) ·svelte-checksin errores propios (73 = deuda ajena) · eidos lint.test + morfo 131/131. Siguiente: F2.4pricing.
-
2026-07-24 — Fix de
Grid+ F2.4pricingHECHOS.- Fix canon
Grid(theming/changelog §47):align/justify/alignContentNO aplicaban — los atajosplace-items/place-contentcon fallbackrevert-layer(cuando su prop no está puesto) revertían los longhands a su inicial, pisándolos. Ahora el fallback COMPONE los vars de los longhands. 353 tests eidos verdes, grids por defecto sin cambio. Encontrado alineandofeature-split. pricing(compound CON CONTEXTO — el primero del tier cuyas partes se COORDINAN, no solo se repiten):<Pricing bind:period>+.Header+.Switch+.Plans+.Plan(featured, badge) +.PlanName/.PlanDescription/.PlanPrice/.PlanFeatures/.PlanFeature/.PlanAction. ElSwitch(ToggleGroup) escribe el periodo en un contexto reactivo (context.ts, getter sobre el$bindable) y cadaPlanPricelo lee → muestra el snippetmonthly/annual. El block NO formatea moneda: la app componeFormatNumberen los snippets (B-7)..PlanActionfija el CTA al borde inferior (margin-block-start: auto) para alinear los botones;.Planconalign="start"deja los checks en columna. Verificado: el toggle cambia los 3 precios a la vez (0/29/99 → 0/23/79), featured con acento+elevación, claro/ oscuro/RTL. Hueco a decisión del usuario: tabla de comparación (features × planes) — otra disposición, candidato a hermanopricing-table. Gates:blocks:checkverde (5 blocks) ·svelte-checksin errores propios. Siguiente: F2.5testimonials.
- Fix canon
-
2026-07-24 — F2.5
testimonialsHECHO. Compound (.Itemrepite, SIN contexto — como feature-grid):<Testimonials>+.Header+.Items+.Item+.Quote+.Author(slotavatar+.AuthorName/.AuthorRole).AutoGridde tarjetas de igual alto;.Authorfijada al borde inferior (marginTop="auto") para alinear las caras aunque las citas midan distinto. La cara es del app (Avatarcon imagen oAvatar.Fallbackde iniciales).Cardpara el quote =variant="outline"(soft neutral es casi invisible en claro). Encontrado: el tema activa solo un SUBCONJUNTO de escalas donor —green/indigo/orange/plum/teal; una escala no activada (cyan/ruby/amber/jade) encolorcae en silencio aprimary(la regla[data-color]hacevar(--scale-…, primary)). No es bug de componente, es config del tema; usar escalas activadas. Hueco a decisión del usuario: la cita única en spotlight (grande, centrada, logo+avatar+autor) — la variante modal del dossier, otra disposición → candidato a hermanotestimonial-spotlight. Verificado en claro/oscuro/RTL, caras alineadas, 5 colores distintos. Gates:blocks:checkverde (6 blocks) ·svelte-checksin errores propios. Siguiente: F2.6faq. -
2026-07-24 — F2.6
faqHECHO. Compound (.Itemrepite) y proxy fino delAccordiondel canon (regla del handoff: leer su API, no reinventar el disclosure):<Faq>+.Header+.List+.Item..ListES elAccordion— toda su API pasa tal cual (type,bind:value,collapsible,variant,size; defaults FAQ: single, collapsible, outline)..ItemproxyaAccordion.Item > Header > Trigger(snippetquestion) /Content(children), y autogenera elvaluecon$props.id()si no se pasa.Containerestrecho (md). El teclado/ARIA del disclosure salen delAccordion, el block no los toca. Hueco a decisión del usuario: la lista estática 2/3 columnas (6 de 7 en TW NO son acordeón) — otra disposición, proplayouto hermano. La cola «¿aún tienes dudas?» es app-land (la demo la pone tras.List). Verificado: el acordeón abre/cierra, claro/oscuro/RTL. Gates:blocks:checkverde (7 blocks) ·svelte-checksin errores propios. Siguiente: F2.7stats-band. -
2026-07-29 — F2.7
stats-bandHECHO. Compound (.Statrepite, sin contexto):<StatsBand>+.Stat+.Value+.Label. Compone elMetricsdel canon para la semántica de KPI —surfaceless, que es lo correcto: una banda no son tarjetas de panel— yCountUppara la cifra que se cuenta sola al entrar en pantalla: la superación literal del dossier, porque ninguna referencia puede shipearla (todas entregan markup estático).countes OPT-IN, nunca default (una cifra que se anima sin que el lector lo pida es ruido, y «99,98 %» no es contable). El block NO formatea ni escribe: el separador de millares sale deuix.formatDENTRO delCountUpy las palabras entran por children (B-7). Escalonado estructural (data-staggeren la banda, cada.StatunMotion trigger="viewport"). Verificado en navegador: 4 cifras con índices 0,1,2,3 contando (9377→11.678 · 255→318 · 36→45), el porcentaje quieto, separador por locale, cero errores de página. Gates:blocks:checkverde (8 blocks) ·svelte-checksin errores propios. Siguiente: F2.8cta. -
2026-07-30 — F2.8
ctaHECHO. Slots de snippet (eyebrow·title·description·actions·children), misma forma quehero: las partes de un CTA no repiten ni coordinan. ComponeSection+Container+Motion+Surface+Heading+Text+Flex. Dos disposiciones:center(Stack, cierre de página) yjustified(Grid2 col., el hueco que nombraba el dossier), que apila en estrecho. Un soloMotion trigger="viewport"conscale-fade: el panel llega ENTERO, porque un CTA es una sola afirmación y repartir sus tres partes se leería como duda. Landmarksection aria-labelledbyal título que el propio block escribe.Tres hallazgos medidos en navegador, no a ojo (los tres van a los gaps del README y a §6 de
PLAN-blocks-quality.md):variant='soft'eliminado de la API: su track queda aoklch(0.9932)contra un--color-surface-defaultdeoklch(0.9911)— 0.002 L, o sea ningún panel en claro — ySurfaceno tiene borde al que caer. Un panel sosegado con borde no tiene primitivo (Card outlineacota pero no acepta acabado de degradado).- La ranura
contrastde la paleta es#ffffffen todo escalón sólido, así que un lienzo de luminancia media deja el cuerpo por debajo de AA: medido,primary5.18 ·indigo5.21 ·plum4.75 pasan en ambos modos, mientrasneutral3.32 ·secondary3.30 ·slate3.30 ·teal3.07 fallan en claro. El block reenvía cualquiercolor; la demo solo ofrece los que pasan. Text alignes inerte por defecto:Textrenderiza unspanytext-alignno hace nada sobre una caja inline, así quealign="center"dejaba la copia alineada a la izquierda dentro del layout centrado. Resuelto conas="p"(que es lo que ese contenido es).
También:
Groupno apila — las acciones necesitaronFlex direction={{ base: 'column', sm: 'row' }}porque a 420px la etiqueta de la acción secundaria se parte contra el botón primario (herocompone sus acciones conGroup, así que arrastra el mismo comportamiento — anotado para su siguiente pasada). El CTA que navega usa el patrónButton child+contentconintent="fulfill", igual quehero. Verificado en navegador:centeryjustifieden claro/oscuro/RTL + 420px, tres colores, entrada disparada (data-animation-pendingfuera), descripción en<p>centrada, cero errores de página. Gates:blocks:checkverde (9 blocks) ·svelte-checksin errores propios. Siguiente: F2.9newsletter. -
2026-07-30 — F2.9
newsletterHECHO. Slots de snippet;center+justified;paneles un interruptor real (no un fallback). La fila esGrid templateColumns={{ base: '1fr', sm: '1fr auto' }}: el campo se queda la pista libre y la acción abraza su contenido, y bajosmpasa a una columna porque un botón al lado de un campo de correo deja inservibles a los dos. El block no valida ni emite sema:Formposee el runtime y ya declaracommit-submit/signal-invalid; el app posee esquema, valores y handler.Superación del dossier: la nota de privacidad (el único hueco que el dossier nombraba para este block) y la etiqueta. Las referencias shipean la fila escondiendo la etiqueta con
sr-onlyo dejando solo un placeholder; aquí se componeField floatingLabel—la etiqueta arranca dentro del control y sube al borde al enfocar o rellenar—, así que la fila queda alineada CON etiqueta real y asociada. Verificado con pulsaciones reales en Playwright: 10px dentro en reposo → −11px sobre el borde al enfocar y al rellenar. (Esto corrige la advertencia de 2026-07-05 de que el flotado no era verificable sin navegador real: el pane suspendido no valía, Playwright headless con teclas de verdad sí.)Tres hallazgos de canon más, medidos (F18/F19/F20 en
PLAN-blocks-quality.md§6):- La fundación de eidos no trae reset de modelo de caja. Recetas como
[data-field-control]declaraninline-size: 100%+ padding, así que bajocontent-boxel control mide 30px más que su contenedor y se metía por debajo del botón: campo 480 / control 510 en la galería frente a 502 / 502 en los docs de componentes, que sí resetean. El framework lo asume del app. Arreglado en app-land (web/routes/blocks/_lib/reset.css), con A/B sobre los 10 previews y 5 páginas de shell: cambia el newsletter y nada más. Hubo que importarlo DOS veces porque la galería arranca UIX en línea en vez de pasar porBootUix(deuda del arnés, anotada). onValidSubmites un no-op silencioso cuando se pasa unformya construido: el componente solo lo reenvía alcreateFormque hace él mismo. El envío validaba, limpiaba y no anunciaba nada.- Los mensajes de SIUM son idlangref; la vía correcta es
uix.langs.t(issue.message, issue.params)(verificado: sale «Debe ser una dirección de correo válida»), pero la demo de docs del propioFormparte la cadena a mano tras el|y por eso siempre muestra inglés.
Verificado en navegador el arco completo: correo inválido → error TRADUCIDO con
role="alert",aria-invalid,aria-describedbyy foco al primer error; correo válido → confirmación del app (Calloutafirmativo con la dirección) y error limpio. Claro/oscuro/RTL, tres colores,panelsí/no y 420px (fila apilada). Cero errores de página. Gates:blocks:checkverde (10 blocks) ·svelte-checksin errores propios · prettier limpio en mis ficheros. Siguiente: F2.10site-footer. - La fundación de eidos no trae reset de modelo de caja. Recetas como
-
2026-07-30 — F2.10
site-footerHECHO. El block donde la regla de forma del tier se ve mejor:.ColumnSE REPITE → parte compound; marca, alta, social, legal y extra no se repiten ni coordinan → slots. Landmark<footer>real, así que escontentinfosin pedir nada. Las columnas van enAutoGrid(minChildWidth, cero breakpoints): el mismo código sirve para 2 grupos o para 5, y en móvil caen a dos. Entrada solo en la región superior y sin escalonado — una línea de copyright que aparece con fundido es teatro, y nadie lee un pie columna por columna.Los dos huecos que nombraba el dossier, cerrados: la banda de alta al boletín EN el pie (3 de 7 en TW) como slot
signup—el app compone su propioForm, el block NO importa el blocknewsletter(B-10)— y el selector de idioma comoextra, vivo sobre el eje real: escribeuix.prefs.setIntent('language', …)y se verificó que muevedocument.documentElement.langaen.Tres números que salieron de medir, no de suponer: el
containerbajó dexlalg(conxlel pie no se alineaba con ninguna sección de la página compuesta); la rejilla superior pasó de1fr 2fra1fr 3fr(con2frun pie de cuatro grupos se partía en 3+1); y el gap de columnas es 6, no 8 (con 8, cinco grupos no comparten fila al ancholg— 728px justos).Hallazgos (F21/F22 en
PLAN-blocks-quality.md§6): (1) ningún primitivo de layout puede cambiar de elemento —Text/Headingaceptanas, peroBoxy todo lo construido sobre él es un<div>fijo, así que una columna de enlaces no puede ser<ul>/<li>; (2) unSelectcontrolado muestra el VALOR crudo hasta que se abre una vez:getDisplayText()resuelve contra el registro de etiquetas que llenan losSelect.Itemal montarse, y con elContenten un portal cerrado no hay ninguno montado. La demo lo rodea con elchilddeSelect.Value.Verificado en navegador: 2/3/4/5 columnas, banda de alta sí/no, claro/oscuro/RTL y 420px (2×2 columnas, fila de correo apilada), cero desbordamiento horizontal, separador
aria-hidden, los tresIconButtoncon nombre, y el selector de idioma cambiando el idioma de verdad. Cero errores de página. Gates:blocks:checkverde (11 blocks) ·svelte-checksin errores propios · prettier limpio. Siguiente: F2.11banner. -
2026-07-31 — F2.11
bannerHECHO. El block más FINO del tier a propósito: cuando el canon ya tiene la pieza, el block es colocación y nada más. Reenvía la superficie delBannerconOmit<BannerProps, 'children'>(no re-declaraintent/variant/sizeni los estrecha), lo mete en columna conContainer(width="100%"+paddingX={0}: a sangre por fuera, en columna por dentro), envuelve conWrapen vez de una fila rígida, y cableaBanner.Closesolo si llegaonDismiss. La demo lo enseña encima delsite-headerde verdad, con página para desplazarse: un aviso dentro de un recuadro no se parece a un aviso.Hallazgo real (fase 0 lo anticipó):
Bannerestamparole="banner"DESPUÉS de sus rest props, así que no se puede relajar; con elsite-headeren la misma página quedan dos landmarksbanner(medido). Único paliativo hoy: nombrarlos conaria-label.Y una lección de método que costó una sesión: reporté tres «defectos del canon» (
aria-labelausente enBanner.Close,typedesaparecido de todoButton,IconButtontragándose elonclick) y los tres eran falsos. La causa: medía demasiado pronto. Losdata-variant/data-sizelos escribe el componente de eidos al renderizar —están desde el primer frame—, perotype,aria-labely los handlers los aplica la runtime del morfo en un efecto posterior. A ~1s:typeausente en 3 de 3 botones y ningún clic disparando; a ~6s:type="button",aria-label="Descartar"y el descarte funcionando. La espera válida es un atributo que solo pueda haber puesto la runtime (waitForFunction(() => boton.hasAttribute('type'))), no que el nodo exista ni que se vea. Regla afinada enCONTINUE-blocks.md.De paso, un aviso de soma que sí era real y era mío:
Tabs.Listsin nombre accesible enBlockDemo.svelte— afectaba a las 12 demos del tier.Verificado en navegador: los 4 intents, los 3 tratamientos,
descartablesí/no (connoel botón deja de renderizarse, no se oculta), claro/oscuro/RTL y 420px, la tira siempre por encima de la cabecera, cero desbordamiento horizontal, y los controles de la demo moviendo la vista previa en línea. Gates:blocks:checkverde (12 blocks) ·svelte-checksin errores propios ·docs:checksin errores míos · prettier limpio. Siguiente: F2.12team. -
2026-07-31 — F2.12
teamHECHO. Compound porqueMemberSE REPITE, y el primero del tier cuyo contexto sirve para una sola cosa: elalignde la sección viaja a cadaMembery a su fila de enlaces, así que la foto, el nombre, el cargo y los enlaces comparten eje sin que el app repita la alineación en cada tarjeta. Unalignexplícito en una parte siempre gana. Es la diferencia confeature-grid, cuyos ítems solo se repiten y por eso no tienen contexto: la regla se aplica parte por parte, no block por block.Dos decisiones de forma: (1) el nombre es un
Heading level={3}apagado consize="sm"—una persona en una retícula tiene nombre y las referencias lo marcan igual—, nivel = estructura, tamaño = tipografía; (2) la fila de enlaces va pegada al fondo (marginTop: auto), porque con biografías de distinto largo las filas quedaban a alturas distintas y la retícula se leía descuadrada — sin biografías las tarjetas ya miden lo mismo y la regla no cambia nada.Encontrado al verificar: un
columnsFIJO no colapsa. A 420px, cuatro columnas dejan celdas de ~90px con el nombre partido en tres líneas y el avatar desbordando. El default del block es fluido (minChildWidth), así que el fallo era de la demo: ahora pasacolumns={{ base: 2, md: N }}— para eso están los props responsivos. Anotado en el README.Verificado en navegador (esperando el atributo que pone la runtime, no el reloj): 2/3/4 columnas,
aligncenter/start propagándose por contexto al eje de cada miembro, biografía sí/no, claro/oscuro/RTL y 420px, seis avatares, doce botones de icono con nombre propio por persona, escalonado estructural con índices 0,1,2…, cero desbordamiento y cero errores de página. Gates:blocks:checkverde (13 blocks) ·svelte-checksin errores propios ·docs:check0 · prettier limpio. Siguiente: F2.13contact. -
2026-07-31 — CAMBIO DE DOCTRINA (decisión del usuario): «si al final es una lista de componentes… el bloque es coordinación, estado, y data también». Con el
contactdelante se vio el motivo: con el estado fuera, cadadisabledes una expresión distinta montada en el punto de uso y aparecen huecos — un envío que no se puede hacer y nadie dice por qué. Escrito enarchitecture/blocks.md§«Coordination», con B-5 (el block puede traer la FORMA de sus datos) y B-7 (posee las palabras de SUS estados, como idlangref con fallback inglés) enmendados y la convención de servicios acotada: el traductor es el único servicio sancionado. No aplica a todos: los blocks de layout no coordinan nada y inventarles estado sería el error contrario. -
2026-07-31 — F2.13
contactHECHO, el primero que COORDINA. Posee tres cosas: la máquina de la sección (state.ts, 7 estados derivados de una sola fuente), la forma por defecto de sus datos (schema.ts) y las palabras de cada estado.CONTACT_REASONyCONTACT_ACTIONsonRecordEXHAUSTIVOS: el mismo mapa que bloquea el botón escribe la frase, así que un estado mudo no se puede ni escribir. Las partes leen el estado por contexto — ninguna lo recalcula ni inventa undisabled. A la demo se le cayeron el esquema, las etiquetas, eldisabled, el flag de envío y la frase: 249 líneas menos, y lo que queda es lo que de verdad es del app.Encontrado al verificar — y era real: la máquina leía
form.isValid, que convalidationBehaviour: 'progressive'significa «aún no se ha encontrado nada mal». Un formulario vacío e intacto no tiene errores → se declaraba válido →incompleteera INALCANZABLE al cargar y la sección pedía resolver la verificación con los tres campos vacíos: justo el hueco que la doctrina existe para cerrar. Arreglado preguntando al ESQUEMA (validateSyncde SIUM: síncrono, y sin escribir errores en el formulario —form.validate()habría encendido los tres campos en rojo al cargar). Fijado constate.test.ts(9 casos), primera lógica pura del tier y por tanto su primer test unitario.Lección de verificación (cara): perseguí un fallo inexistente durante varias rondas porque mi guion esperaba
button[type="submit"]con atributotypecomo puerta de hidratación — y ese atributo viene ya en el HTML del servidor, así que la espera se cumplía sobre el documento estático y yo tecleaba antes de que Svelte tomara los inputs: los valores entraban en el DOM yform.valuesseguía vacío. Matiza la regla del handoff: no basta con que el atributo lo escriba la runtime en cliente, tiene que ser algo que NO exista en el SSR. La puerta honesta aquí fue<style id="uix-blocks-display">(lo escribe un$effectdeBootUix) más un ida y vuelta reactivo real. También costó una pista falsa: el servidor de dev servía módulos rancios y elconsole.logdel derivado salía por el stdout del servidor (SSR), no por la consola del navegador — que es exactamente lo que delató el diagnóstico.Verificado en navegador, arco completo y las dos ramas: sin reto vacío → un campo → tres campos (se desbloquea) → «Enviando…» → «Enviado» con la frase afirmativa; con reto, con los tres campos llenos el botón sigue bloqueado y la razón pasa a «Resuelve la verificación de arriba» — es decir, nombra el bloqueo REAL y en orden. Cero errores de página. Los estados
verifyingyrejectedquedan cubiertos por test unitario, no por el reto en vivo: el provider deProofOfHumanescribestatusél mismo y el veredicto del app no se sostiene (hilo de canon abierto). i18n: el arnés registra el namespaceblocksen las DOS cadenas de arranque (galería y previews) y la demo se lee en castellano. Añadido el control vivo que faltaba para un prop público:verificationcon reto / sin reto — y omitir el prop NO es lo mismo que pasar un estado que nunca verifica. Gates:blocks:checkverde (14 blocks) ·svelte-checksin errores propios ·state.test.ts9/9 · prettier limpio en los ficheros propios. Siguiente: F2.14content-section. -
2026-07-31 — F2.14
content-sectionHECHO → F2 CERRADA 15/15. El último de sitio, y el que más cerca estuvo de ser un envoltorio:Section+Container+Proseno habría añadido nada, porqueProseya trae su medida de lectura. Lo que sí falta en el ecosistema es el escape: dentro de unContainernada puede ser más ancho que él, así que una figura a sangre hay que sacarla del artículo y ponerla de hermana — y el orden de lectura se rompe. La sección es una REJILLA de 5 pistas (canalón · flanco · MEDIDA · flanco · canalón) y cada parte dice hasta dónde llega:measurecol 3,widecol 2/5,fullcol 1/-1. Todas las longitudes son tokens del sistema (--measure-*,--container-width-*,--container-padding-inline)..Bodyy.Mediason compound porque SE REPITEN (un artículo alterna prosa y figuras en el orden en que se lee); la cabecera son slots de snippet..Bodyapaga la medida deProse: dos dueños del mismo ancho se pelean y ganaría el más estrecho en silencio.Tres defectos que solo dijo el navegador, ninguno visible leyendo el código: (1)
gapsepara también las COLUMNAS, y una parte que las cruza se lleva esos huecos encima —widemedía 1088 en vez de los 1024 del contenedor con el que debe alinearse, y a 420pxfullllegaba a 532 en una rejilla de 404 y hacía scrollear la página; las columnas aquí son un instrumento de medida, no cosas puestas al lado, así que van conrowGapycolumnGap={0}. (2) una pistamin(medida, 100%)ignora sus propios canalones: a 420px la central se quedaba con los 404 enteros y los canalones empujaban la rejilla a 436. (3)100%significa algo distinto dentro de cada parte — enmeasure/wideya es una pista, enfulles la rejilla entera con canalones incluidos —, así que capar el pie igual en los dos casos lo dejaba a 340 contra una columna de 372 en uno y a 404 en el otro.Verificado tras los arreglos en 3 viewports × 3 medidas × 3 anchuras: el pie tiene el mismo ancho y la misma x que la prosa en las 18 combinaciones, cero desbordamiento horizontal. Claro, oscuro y 420px mirados. Semántica propia
figure/figcaption(no interactivos → estructura de documento, B-8) con el hueco señalado: el canon no tiene primitivaFigure. Contexto de CONFIGURACIÓN, no de estado (la medida, para el pie) — es un block de layout y no coordina nada. Gates:blocks:checkverde (15 blocks) ·svelte-checksin errores propios ·docs:check0 · prettier limpio. F2 CERRADA; siguiente F3 (blocks de aplicación) o la página compuesta de prueba del cierre de F2. -
2026-07-31 — Cambio de CANON pedido por el usuario: un componente no es librería de otro. Al tipar
content-sectionimportéTextMeasuredesdecomponents/text/types, y el usuario lo cortó: no se importan librerías de otros componentes; lo compartido se saca a una librería común. Al mirarlo, la violación ya existía y era mayor que la mía —Headingimportaba CINCO escalas tipográficas deText(TextTracking,TextLeading,TextWrap,TextNumeric,TextMeasure). Las cinco se han movido asrc/uix/eidos/lib/types.ts, que es donde su propio encabezado dice que van («only types that would otherwise duplicate across components live here») y donde ya está el precedente documentado del mismo fallo (ColorRolere-escrito a mano que derivó a 8 miembros contra 9). Sin shim de compat:text/types.tslas importa,heading/types.tstambién, y el bloque igual. Arrastraba además un error mío anterior: había declaradoContentMeasure = 'narrow' | 'normal' | 'wide', copia literal del canónico, y tipadowidecomostringlibre con default'var(--container-width-lg)'— lo que empujaba la construcción del nombre del token a un fichero de RUTA. AhorameasureesTextMeasure,wideesContainerSizey el únicovar()vive engrid.ts. Gates tras el movimiento:checksin errores en lo tocado (74 globales, ninguno mío) · 28 de 29 suites de eidos verdes (la que falla esaudio-playersin morfo, del hilo de audio,c4d64ba98) ·blocks:check15. -
2026-07-31 —
content-section: los tres ejes pasan a RESPONSIVE (decisión del usuario: «debe de ser responsive»).measure,widey elwidthde cada parte aceptanResponsiveProp, resueltos coneidos.resolve()— el mismo camino que usan los primitivos de eidos, así que los únicos breakpoints en juego son los canónicos. Es lo que un valor único no puede decir:{ base: 'full', md: 'wide' }= foto a sangre en móvil y contenida demdarriba. Verificado contra el breakpoint canónico: a 767 va a sangre, a 768 se contiene. Expuesto como control vivo en la demo. -
2026-08-01 —
content-section: un cuarto defecto, y lo cazó el usuario mirando la pantalla. Yo había verificado la geometría INTERNA del block (medida, escape, pie, breakpoints, responsive) pero nunca lo había mirado contra la página: la pistawideusaba--container-width-lga pelo, que es el ancho EXTERIOR delContainer, así que la figura sobresalía 16px por cada lado respecto al texto de la sección hermana de debajo — a 1280, 1440 y 1920, siempre los mismos 16. El README afirmaba justo lo contrario («lines up with the sections above and below»). Arreglado restando2 * --container-padding-inline: la pista ancha enlgson 992, no 1024, y el desfase pasa a 0 en los tres anchos. Verificado también en el Chrome del usuario.Lección de método: medir el block contra SÍ MISMO no basta. Un block vive en una página, así que la comprobación que faltaba era poner una sección normal debajo y comparar los bordes izquierdos. Y generé la captura en oscuro y nunca la abrí — la regla dice screenshot + MIRAR, y yo me quedé en la primera mitad. Ojo también con inspeccionar en una pestaña de fondo:
visibilityState: 'hidden'suspende el rAF y deja losdata-animation-pendingpuestos conopacity: 0, lo que parece un bug de visibilidad y no lo es (en primer plano, 0 pendientes).