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

61 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 8 componentes base del CANON que los blocks necesitan (7 + nav-tree por E-1) en curso — 7/8 HECHAS (empty-state · result · callout · sticky · prose · anchor-nav · nav-tree, PASS las siete; ver registro). Resta: sidebar (F1.7)
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.md ANTES 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):

  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 (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: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

✅ 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) · 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.

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-view propio.
  • Morfo (boceto): parts provider (nav landmark, aria-label vía texts:) + list + item + trigger (grupo colapsable) + link (compone Link; aria-current="page" del MISMO estado que data-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.
  • 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ía dom.
  • 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 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.

F2.11 banner (E-3)

  • Compone: el componente Banner existente + Container + Link + Button (dismiss lo posee Banner si ya lo trae — leer su README).
  • API: <SiteBanner> + children; posición top pareja de site-header.
  • 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 (E-3)

  • Compone: Section + AutoGrid + Avatar + Heading + Text + Group (IconButton sociales por miembro).
  • API: <Team> + .Header + .Member (+.MemberAvatar/.MemberName/ .MemberRole/.MemberLinks).

F2.13 contact (E-3)

  • Compone: Section + Grid (info + form) + Form + Field + Textarea + Button + filas de datos de contacto (Icon + Text + Link).
  • API: <Contact> + .Info + .Form; validación y submit = sistema Form con handlers del app (misma regla que newsletter).

F2.14 content-section (E-3)

  • Compone: Section + Container (medida estrecha) + Prose (F1.5) + slots de media (Image/Figure fuera del flujo prose).
  • API: <ContentSection> + children (el HTML renderizado va al Prose).
  • Depende de: F1.5.

(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 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, 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 (Group de dos Card/Link).
  • E-5 firmada: el atajo ⌘K del trigger de búsqueda = listener app-land documentado en el README del block; el art shortcuts conserva 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 + 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).
  • 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-tree data-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 en next-features.md §9; E-5 ⌘K = listener app-land (nota en F4.1). Doctrina (architecture/blocks.md promotion path) y galería actualizadas.
  • 2026-07-21 — F1.2 empty-state HECHA (ruta de 9 fases completa, suelo E-2 del dossier §P3): morfo display 5 partes (scope: ['eidos'], 0 eventos justificados, Title role:'heading') + langs label + eidos compound (Media kind=icon|media con placa 2× glifo · Title level 2–6 default h3, modelo Atlaskit · Description measure 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:audit PASS · eidos-lint 5 morfo-backed + 4 eidos-only sancionados · svelte-check 76E/51W = baseline exacto · navegador claro Y oscuro por estilos computados (título 18→24px, placa 40→64px, chips vivos). Siguiente: F1.3 result (comparte esqueleto; fase 0 contra dossier §P3).
  • 2026-07-21 — F1.3 result HECHA (ruta completa, suelo E-2 §P3): morfo 6 partes (extra sin archetype — parte genuinamente propia) · status enum de 7 CON warning (decisión del dossier firme), default info, HTTP renombrados semánticos y renderizados NEUTROS (código mono grande, jamás rojo — doctrina AntD) · media default compone el mapa doctrinal IntentIcon (fulfill/threat/risk) + Info de 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:audit PASS 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.4 callout (decisión IMPORTANT/5º hueco + role=note; dossier §P3).
  • 2026-07-21 — F1.4 callout HECHA (ruta completa, suelo E-2 §P3): morfo 4 partes + texts con 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) + color override 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ía aria-labelledby SOLO mientras está montado (registro reactivo por context; bug cazado en navegador: el += del registro leía el estado dentro del tracking del $effect del hijo → effect_update_depth y contador a −997 — fix untrack, lección para la memoria) · Title NO heading (protege el outline de anchor-nav) · icono = mapa doctrinal IntentIcon · escalación tipada role: status|alert|none (el role="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.1 sticky (orden del plan: desbloquea F2; dossier §P3 — offsets, scroll-host, centinela por borde, espejo scroll-state).
  • 2026-07-21 — F1.1 sticky HECHA (commit 998b11d9b, 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 por syncAttrs (nunca dom.apply crudo). Diseño: un StickyProvider registra AMBAS partes (box+sentinel) en un runtime; $effect retorna el observer como teardown; callback async voltea stuck=$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-edge espejan @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 el installSomaHarness compartido (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 con aura sin commitear de sesión paralela). Consola: detectado un warning reactivo residual en callout (registerTitle en $effect) — a investigar. Siguiente: F1.5 prose (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 a top/bottom fí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 prose HECHA (commit 5b84749ee, 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 — sin not-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-nav HECHA (commit fe2d43b7a, 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 en data-active; indent por data-level. Verificación: test real-IO chromium (anchor-nav-io) — banda + zona muerta al fondo + fallback inicial; el pane suspendido daba un getComputedStyle de color OBSOLETO (falsa alarma, la regla [data-active] sí aplica — el font-weight:500 lo 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 y outline-offset tokenizados (--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-level fuera de rango degrada a flush (no arreglado, simplicidad). Además (37e35d7a2): npm run check global destapó 2 errores de tipo en F1.1 sticky (sentinelRef debía ser State no Active; eidos types importaba StickyProps en vez del export renombrado ProviderProps) — 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-tree EN 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 + eventos emerge, pack espeja collapsible); filas propias (no Link/Badge compuestos — Badge diferido a Gap); activeHref como 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-tree HECHA (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 parte group es el target de los eventos y se registraba SIN ref, así que runtime.trigger lanzaba SomaRuntimeTargetError en silencio (lo cazó el navegador: cero data-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 en parentByKey (clave duplicada = nodo padre de sí mismo → trailKeys giraba para siempre y congelaba la pestaña) → clave sufijada + aviso del logger + guarda de ciclo en el paseo; (4) colapso ahora CONTEXTUAL (recuerda el activeKey bajo 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) child ya no se comía el árbol (recibe children además de props — 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-width fuera 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): badge SÍ 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 parte badge y renderiza el snippet recibido (sin él, el valor crudo: sigue headless), el wrapper de eidos pasa el Badge canónico; va DENTRO del control de la fila para que su texto entre en el nombre accesible («TSC, New, enlace»). disabled también entra, por decisión explícita del usuario tras presentarle el criterio: el nodo conserva la fila pero pierde el href (no hay nada que activar con Enter, clic ni «abrir en pestaña nueva» — un pointer-events:none de CSS solo tapa el ratón) y añade aria-disabled + data-disabled; el toggle del grupo NUNCA se deshabilita (disclosure es control de vista, no destino) y un nodo sin href lo ignora. Verificado: component:audit PASS · eidos-lint 26 morfo-backed / 0 invalid · svelte-check 0 errores en mis archivos · vitest src/uix/eidos 353/353 · navegador real (Playwright, el pane suspendido NO sirve: congela rAF y el scroll-into-view parecía roto) → trail auto-expandido, sema emerge-expand/collapse estampando en group, teclado nativo (Tab/Enter/ Espacio), foco visible, claro+oscuro, RTL, 375px sin desbordes, 0 errores de consola. F1 = 7/8. Resta: sidebar (F1.7).

Powered by TurnKey Linux.