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

43 KiB

PLAN — Tier blocks: composición reutilizable (infraestructura + componentes base + catálogo)

Kickoff para sesión nueva: "Lee docs/process/PLAN-blocks.md y continúa la fase que toque." Decisión de usuario (2026-07-21): existe un tier nuevo blocks — 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-shell es un chasis compartido; el tier packs (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 7 componentes base del CANON que los blocks necesitan pendiente
F2 Blocks de sitio (10) 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.

Proceso por tanda (una tanda = un componente o un block):

  1. git reset -q + verificar HEAD (sesiones concurrentes; NUNCA amend).
  2. Fase 0 del ítem: comparativa + scope al usuario si hay decisiones.
  3. Construir. UN fichero → verificar → resto (no-cascade). Componer, jamás re-implementar; gap detectado = FLAG al usuario, no workaround inline.
  4. Verificar: npm run check + scope vitest del ítem + guard del tier (component:audit --only {kebab} para F1 · blocks-check para F2+) + navegador de verdad: screenshot y MIRARLO, claro Y oscuro (colorScheme:'dark'), móvil y desktop para blocks (resize 375/1280).
  5. Demo con profundidad de testbed (B-9); docs del framework en el MISMO pase (README del ítem + mapa si procede).
  6. Commit (convención viva del repo): uix({kebab}): … para F1, blocks({kebab}): … para F2+, docs(blocks): … para doctrina. Stage SOLO los paths propios (nunca git add -A; excluir words/, palabras/, web/routes/alpha/).
  7. 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 (7)

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.7 (desbloquean F4/F3).

F1.1 sticky — afijado con estado

  • Gap: wrapper position:sticky que 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 · Mantine Affix · 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, sin texts. Partes display → sin archetype (un archetype interactivo arrastra estilos de item; y el archetype content pisa position — justo lo que sticky no puede permitirse).
  • Soma: provider observa el sentinel vía dom.observe (IntersectionObserver por adom) — JAMÁS scroll listener + getBoundingClientRect síncrono (regla de reflow: lecturas de layout solo post-layout vía dom.measure/rAF). data-stuck lo 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 con data-stuck presente (documentar el patrón en el README).
  • Demo: página larga con header/toolbar afijable, chip mostrando el estado, edge top y bottom.
  • Trampas: mergeProps clobberea stamps — attrs visuales del wrapper fuera del morfo salvo que crucen a soma (aquí data-stuck SÍ 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: metrics es scope: ['sema','eidos'] sin soma). Morfo-first aplica igual: hasta las hojas llevan morfo.
  • Fase 0, comparar: Chakra EmptyState · AntD Empty · 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/card antes de escribir una línea); centrado, spacing tokenizado --empty-state-*, tamaño vía canon size si aporta (probablemente solo sm/md). actions compone Button del catálogo vía children.
  • Demo: en contexto real — un table/grid-list sin 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. Prop status → data-status (success | error | info | forbidden | not-found | server-error). Cuidado doctrinal: data-status NO es el intent perceptivo — si en algún momento gana eventos con evaluación, el intent viaja por fromProp:intent del 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 Button home/back.

F1.4 callout — admonición inline

  • Gap: aviso DENTRO del contenido (info/tip/aviso/peligro). Banner es el anuncio de página; esto es la nota de documento — imprescindible para prose y 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 · shadcn Alert · admonitions de Docusaurus/Starlight · banner propio (leer su README: qué decisiones ya están tomadas para avisos y cuáles NO trasladan).
  • Morfo (boceto): parts provider (role note) + icon + title + content. La evaluación ES semántica aquí: prop intent restringido a un subset canónico (candidatos: neutral | affirm | risk | threat; validar contra vocabularies.md) estampado vía fromProp: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 banner e 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 — · Mantine TypographyStylesProvider · 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/pre alineados con los tokens de code/code-block (leer sus recetas ANTES; si hay que duplicar valores, es un gap a flag, no un copy-paste); imágenes max-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 · Mantine TableOfContents · Starlight/Docusaurus TOC. APG: no hay patrón de widget — es un nav landmark con aria-current; anotarlo en el README (válvula A-1.4 con nota, como pagination/stepper).
  • Morfo (boceto): parts provider (nav, aria-label vía texts: — 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 compone Link del catálogo). Data: data-active en item; aria-current. Eventos: click de link = navegación (familia/verbo a decidir en fase 1 contra SEMA_VERBS — candidato familia shift; validar). expression: según criterio D.4.
  • Soma: registro de secciones objetivo (por id o por attachPart), observación vía dom.observe (IntersectionObserver, umbrales al gusto del provider) — mismas reglas anti-reflow que F1.1. El activo es estado del provider; los effects estampan data-active. Scroll programático al click vía dom (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

  • Gap: columna de navegación con grupos, item activo, colapso a raíl y variante móvil. navigation-menu es horizontal (patrón Radix); esto es otra pieza.
  • 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) · Mantine AppShell.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 — compone Button con asChild/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 un Link — decidir en fase 1 (si item = Link puro, 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 COMPONE Drawer (no lo reimplementa) — el provider decide qué montar por breakpoint responsive del sistema, no matchMedia propio.
  • Eidos: raíl con will-change cuidado (memoria: will-change: transform produce jitter en raíles finos con DPR≠1 — override a auto); tooltips de item en modo raíl componen Tooltip; tokens --sidebar-* (width, rail-width, paddings).
  • Demo: shell de app simulada, toggle colapso, grupos, móvil (375px) con drawer, RTL.

Cierre F1: los 7 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 (10)

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 con Button) + Badge (chip anuncio) + Image/AspectRatio.
  • API: <Hero> (prop layout: '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: center y split (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/.ItemText o 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 + Icon check + Text).
  • API: <Pricing> + .Switch (billing toggle; estado de vista local permitido §1) + .Plan (prop featured?) + .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 FormatNumber del app).
  • Demo: 3 planes, featured al centro, toggle vivo.

F2.5 testimonials

  • Compone: Section + AutoGrid + Card + Avatar + Text + Group.
  • API: <Testimonials> + .Header + .Item (+.ItemAuthor con 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; type del accordion expuesto tal cual.

F2.7 stats-band

  • Compone: Section + Group/AutoGrid + Metrics + CountUp + Text.
  • API: <StatsBand> + .Stat (valor + etiqueta; CountUp opt-in por prop).
  • v1: banda horizontal 2–4 stats; los formatos numéricos llegan ya formateados o vía FormatNumber compuesto por el app.

F2.8 cta

  • Compone: Section + Surface (tratamiento de fondo del sistema — un CTA se distingue por acabado, y eso ya es vocabulario del framework) + Heading + Text + Group (Buttons).
  • API: <Cta> + .Title + .Description + .Actions.

F2.9 newsletter

  • Compone: como F2.8 + Form + Field (email) + Button.
  • API: <Newsletter> + .Title + .Description + .Form (submit handler del app; validación por el sistema de Form — leer su README: el block no inventa validación).
  • Nota: separado de cta porque el form introduce a11y y estados (invalid/submitting) que el CTA puro no tiene.
  • Compone: <footer> + Container + Grid (columnas: Heading sm + Stack de Link) + Separator + Group (IconButton sociales) + Text legal.
  • API: <SiteFooter> + .Column + .Social + .Legal + .Extra (slot libre: theme/lang pickers del app — D-BLK.6).
  • v1: 3–5 columnas responsive → apiladas en móvil.

(logo-cloud se mueve a F5: su versión honesta pide Marquee o queda en un Wrap trivial que no justifica block todavía — decisión anotada, revisable.)

Cierre F2: blocks-check verde; galería web/routes/blocks/ con los 10; 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 de Form (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 Form con 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 + EmptyState integrado.
  • Compone: Table + SearchField + DropdownMenu + Button + Pagination + EmptyState (F1.2) + Group/Toolbar.
  • ⚠️ Fase 0 OBLIGADA y más profunda: leer soma/components/table + $libs/datagrid ANTES 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 + filas Field/controles + Callout (F1.4, intent threat)
    • 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 con Icon, Separator, Kbd opcional).
  • 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 trigger IconButton) + 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 width por el prop tokenizado del PopoverContent (la lección ya está aprendida — no re-descubrirla).

F3.8 wizard

  • Compone: Stepper + Form + Group de Buttons (atrás/siguiente/ finalizar) + Progress opcional.
  • API: <Wizard> + .Steps (Stepper compuesto) + .Step (contenido) + .Nav.
  • Fase 0: leer el API real de Stepper y de Form (validación por paso) — el block solo coordina visibilidad de paso + navegación; el estado de validez lo dicta Form.

F3.9 error-page

  • Compone: Result (F1.3) + Group de Button/Link + SearchField opcional (404).
  • API: <ErrorPage status=…> + children de acciones.
  • v1: 404 · 403 · 500 · offline.

F3.10 kanban

  • Compone: DragDrop + Grid/Group de columnas + Card + VirtualList (columnas largas) + Badge + EmptyState por columna.
  • ⚠️ Fase 0 OBLIGADA: leer soma/components/drag-drop a 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 en next-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, sidebar, 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: Sidebar (árbol de docs) + Prose (main) + AnchorNav (TOC derecha) + Breadcrumb + trigger de búsqueda (Command — ya existe) + prev/next (Group de dos Card/Link).
  • 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 + Toolbar opcional.
  • 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)

  1. npm run check sin regresión; npm run blocks:check verde; npm run component:audit → los 7 de F1 PASS (o NEEDS-WORK solo por D-* de la fase demos global); docs:check verde.
  2. 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.
  3. Doctrina publicada y enlazada (architecture/blocks.md en el mapa; CLAUDE.md al día); cada block con README B-9 completo.
  4. 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 en next-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/ (no src/blocks/); referencias del plan actualizadas (tabla de tiers, B-4, blocks-check, F0.3/F0.4). Resto firmado como propuesto. Siguiente: F0.2 (doctrina en architecture/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ía web/routes/blocks/ con layout de bootstrap propio (+layout@.svelte, espejo mínimo del de /uix, sin packs sema) · fila E1 en docs/README.md. Nota de commit: docs/README.md y docs/next-features.md quedaron 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).

Powered by TurnKey Linux.