61 KiB
PLAN — Tier blocks: composición reutilizable (infraestructura + componentes base + catálogo)
Kickoff para sesión nueva: "Lee
docs/process/PLAN-blocks.mdy continúa la fase que toque." Decisión de usuario (2026-07-21): existe un tier nuevoblocks— conjuntos de componentes desempeñando una función (cabecera sticky, hero, footer, app-shell…). Este plan es autosuficiente: cada fase lista QUÉ leer, QUÉ producir y CON QUÉ guard se verifica. Un agente no debe descubrir la doctrina por arqueología — este documento la enlaza toda.Antes de escribir código de F0: presentar al usuario las decisiones D-BLK de §2 (AskUserQuestion o tabla en chat) y obtener firma. Las propuestas de este plan son eso — propuestas razonadas, no decisiones tomadas.
0. Contexto y estado
- Origen: análisis del ecosistema (sesión 2026-07-21). Diagnóstico: el
catálogo de primitivas es excepcional (~140 componentes eidos, ~95 con soma);
la brecha está en (a) piezas de contenido/estado de página y (b) el nivel de
composición. Iniciativa registrada en
docs/next-features.md§8. - Precedentes en el repo: la familia
chat-*es un "bloque" construido como componentes canónicos (siguió la ruta de 9 fases porque cada pieza tiene contrato real);picker-shelles un chasis compartido; el tierpacks(docs/architecture/packs.md) ya resolvió la pregunta "¿cómo vive un tier fuera del canon?" — este plan lo usa de espejo. - Estado: F0 pendiente (nada construido). Actualizar esta tabla al cerrar
cada tanda, estilo
PLAN-component-coherence.md.
| Fase | Contenido | Estado |
|---|---|---|
| F0 | Infraestructura del tier: doctrina + alias + guard + rutas demo | HECHA 2026-07-21 (F0.1–F0.7; cross-ref en comparison.md omitido a propósito — sin aporte hasta que exista catálogo) |
| F1 | 8 componentes base del CANON que los blocks necesitan (7 + nav-tree por E-1) |
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.mdANTES de diseñar — 6 pistas de investigación con suelos de paridad a nivel de prop, pitfalls y ángulos de superación por ítem. La fase 0 verifica contra el dossier (y solo investiga de cero lo que el dossier no cubra); las brechas de suelo que el dossier señala para el ítem se resuelven en su scope-approval.
Proceso por tanda (una tanda = un componente o un block):
git reset -q+ verificar HEAD (sesiones concurrentes; NUNCA amend).- Fase 0 del ítem: comparativa + scope al usuario si hay decisiones.
- Construir. UN fichero → verificar → resto (no-cascade). Componer, jamás re-implementar; gap detectado = FLAG al usuario, no workaround inline.
- Verificar:
npm run check+ scope vitest del ítem + guard del tier (component:audit --only {kebab}para F1 ·blocks-checkpara F2+) + navegador de verdad: screenshot y MIRARLO, claro Y oscuro (colorScheme:'dark'), móvil y desktop para blocks (resize 375/1280). - Demo con profundidad de testbed (B-9); docs del framework en el MISMO pase (README del ítem + mapa si procede).
- Commit (convención viva del repo):
uix({kebab}): …para F1,blocks({kebab}): …para F2+,docs(blocks): …para doctrina. Stage SOLO los paths propios (nuncagit add -A; excluirwords/,palabras/,web/routes/alpha/). - Actualizar la tabla de estado de este plan (y
next-features.md§8 al cerrar cada fase).
Prohibiciones: no tocar palabras/, chronos/, media-player
(foráneos/WIP — componerlos solo cuando estén landed; hoy chronos NO lo
está); no crear servicios/mocks falsos en tests (instancias reales vía
createActiveUix); no --no-verify; no borrar nada sin instrucción
explícita; responder en castellano, código y docs en inglés.
F0 — Infraestructura del tier
Objetivo: que exista el tier con doctrina, guard y sitio donde vivir — vacío pero verde.
| Paso | Producir | Verificación |
|---|---|---|
| F0.1 | Firma D-BLK (§2) con el usuario; enmendar el plan si procede | HECHA 2026-07-21 — firmas en §2 (D-BLK.1 enmendada: src/uix/blocks/) |
| F0.2 | docs/architecture/blocks.md — la doctrina de §1 + §3 en formato espejo de packs.md (frontmatter E1, regla de admisión, hard boundaries, contrato B, "promotion path" = la F1 como ejemplo vivido). Enlazar sin copiar: canon → CANON.md, ruta → building-a-component.md |
npm run docs:check (links) |
| F0.3 | Alias $blocks → src/uix/blocks en el const aliases de vite.config.ts (fuente de verdad) + svelte.config.js en sync + fila en la tabla de aliases de CLAUDE.md |
npm run check |
| F0.4 | src/uix/blocks/README.md — mapa del tier (inventario vivo = el árbol, como packs) + template de README de block (B-9) |
— |
| F0.5 | scripts/blocks-check.ts + npm script blocks:check — los checks mecánicos listados en §3. Espejo estructural de packs-check. Test negativo: un fixture con <button> crudo debe fallar |
npm run blocks:check verde en tier vacío + test negativo rojo |
| F0.6 | web/routes/blocks/+page.svelte — galería índice (dogfooding: componer Container/Section/Card/… del propio catálogo para la galería) |
navegador claro/oscuro |
| F0.7 | Cablear docs: fila E1 en el mapa de docs/README.md (architecture/blocks.md) + nota del tier en el diagrama de arquitectura de CLAUDE.md (línea blocks/ → …) + cross-ref en docs/comparison.md si aporta |
npm run docs:check |
Cierre F0: check + docs:check + blocks:check verdes; galería
renderiza vacía con mensaje de "en construcción" compuesto con el catálogo.
CERRADA 2026-07-21 — evidencia: docs:check 0/0 (511 docs);
svelte-check 76E/51W = baseline exacto (los archivos nuevos compilan
limpios; los 76 son deuda foránea preexistente del árbol sin commitear);
blocks:check verde (self-test 10 fixtures OK — el <button> crudo SE
detecta — y 5 532 archivos de canon/arts/libs/packs escaneados sin
violaciones de dirección). Galería verificada en navegador vía árbol de
accesibilidad + estilos computados en AMBOS modos (light: base-light, h1
oklch(0.24…); dark: base-dark, h1 oklch(0.95…)); la captura de píxeles
del panel embebido expiró (renderer suspendido en segundo plano — clase
conocida) — pendiente de un vistazo humano o Playwright en la primera
sesión F1. El listener prefers-color-scheme del layout funciona al boot;
el evento change en vivo no dispara con el panel suspendido (peculiaridad
del entorno, mismo patrón raw-matchMedia que el layout de /uix).
F1 — Componentes base del CANON (8)
Estos NO son blocks: entran por src/uix/{morfo,soma,sema,eidos} siguiendo
la ruta completa de 9 fases de docs/building-a-component.md (fase 0
Decide → 8 Acceptance), con component:audit --only {kebab} como oráculo.
Las fichas siguientes NO sustituyen la ruta — la parametrizan: dan el gap, la
membresía esperada, el boceto de morfo, las referencias mínimas de la fase 0
y las trampas conocidas del ecosistema que aplican. El vocabulario cerrado
(archetypes, familias, verbos, intents, holds) se toma SIEMPRE de
docs/canon/vocabularies.md (generado del código) — las fichas nombran
candidatos, el builder valida contra la lista real.
Orden recomendado: F1.2 → F1.3 → F1.4 (display, baratos, establecen ritmo) → F1.1 (desbloquea F2) → F1.5 → F1.6 → F1.8 → F1.7 (desbloquean F4/F3).
E-2 firmada (2026-07-21): los v1 de F1–F4 se dimensionan al suelo de paridad del dossier (lo que ≥2 referencias convergen) — cada fase 0 trae la subida concreta al scope-approval; quedarse bajo suelo exige decisión registrada, nunca omisión.
F1.1 sticky — afijado con estado
- Gap: wrapper
position:stickyque SABE cuándo está afijado (data-stuck) para que la cabecera cambie elevación/fondo al pegarse. - Membresía: soma + eidos (comportamiento real: observación + estado).
- Fase 0, comparar: AntD
Affix· MantineAffix· la técnica sentinel con IntersectionObserver (CSS-Tricks/web.dev) · shadcn (no lo tiene — anotarlo como diferencial). - Morfo (boceto): parts
provider(el contenedor sticky) +sentinel(1px observado,aria-hidden); data:data-stuck(presente/ausente),data-edge(top | bottom). Sin eventos v1 (afijarse es hecho de layout, no ocurrencia perceptiva del usuario — si algún consumidor pide señal sema, se revisa con el criterio D.4). Sin keyboard, sintexts. Partes display → sinarchetype(un archetype interactivo arrastra estilos de item; y el archetypecontentpisaposition— justo lo que sticky no puede permitirse). - Soma: provider observa el sentinel vía
dom.observe(IntersectionObserver por adom) — JAMÁS scroll listener +getBoundingClientRectsíncrono (regla de reflow: lecturas de layout solo post-layout víadom.measure/rAF).data-stucklo escriben los effects (dom.apply), único escritor. - Eidos: wrapper fino; tokens
--sticky-top-offset/--sticky-z-index(alias de la escala--z-index-*semántica, no número crudo). La receta NO decide sombra/fondo del contenido — eso lo hace el consumidor seleccionando su propio estado visual condata-stuckpresente (documentar el patrón en el README). - Demo: página larga con header/toolbar afijable, chip mostrando el estado, edge top y bottom.
- Trampas:
mergePropsclobberea stamps — attrs visuales del wrapper fuera del morfo salvo que crucen a soma (aquídata-stuckSÍ es soma).
F1.2 empty-state — estado vacío
- Gap: patrón universal icono/título/descripción/acción para listas y paneles sin datos. Hoy no existe nada.
- Membresía: morfo + eidos, sin provider soma (display puro; hay
precedente sancionado:
metricsesscope: ['sema','eidos']sin soma). Morfo-first aplica igual: hasta las hojas llevan morfo. - Fase 0, comparar: Chakra
EmptyState· AntDEmpty· HeroUI · patrones de empty state de Material. - Morfo (boceto): parts
provider+media(icon o ilustración) +title+description+actions. Sin eventos, sin keyboard, sin archetype en partes display.texts:NO — los strings los trae el app (es contenido, no chrome del componente). - Eidos: receta pequeña espejo de la más cercana ya enviada (estudiar
banner/cardantes de escribir una línea); centrado, spacing tokenizado--empty-state-*, tamaño vía canonsizesi aporta (probablemente solosm/md).actionscomponeButtondel catálogo vía children. - Demo: en contexto real — un
table/grid-listsin filas mostrando el empty-state, más la escena aislada con controles.
F1.3 result — página de resultado
- Gap: estado terminal de página/flujo (éxito, error, 403/404/500).
Pareja de
empty-state, distinto rol: cierra un flujo, no describe ausencia de datos. - Membresía: como F1.2 (morfo + eidos display).
- Fase 0, comparar: AntD
Result· patrones de error page de Tailwind UI · HeroUI. - Morfo (boceto): parts
provider+media+title+description+actions+extra. Propstatus→data-status(success | error | info | forbidden | not-found | server-error). Cuidado doctrinal:data-statusNO es el intent perceptivo — si en algún momento gana eventos con evaluación, el intent viaja porfromProp:intentdel morfo, no reciclando status (regla data-color ≠ perceptual-intent). v1 sin eventos. - Eidos: color del media por rol canónico según status (roles, no hex); iconografía por status con override por children.
- Demo: los 6 status + composición con
Buttonhome/back.
F1.4 callout — admonición inline
- Gap: aviso DENTRO del contenido (info/tip/aviso/peligro).
Banneres el anuncio de página; esto es la nota de documento — imprescindible paraprosey el docs-shell. - Membresía: morfo + eidos display (sin soma; variante dismissible se DIFIERE — si se pidiera, el dismiss es evento real → soma + sema en esa pasada, no antes).
- Fase 0, comparar: Radix Themes
Callout· shadcnAlert· admonitions de Docusaurus/Starlight ·bannerpropio (leer su README: qué decisiones ya están tomadas para avisos y cuáles NO trasladan). - Morfo (boceto): parts
provider(rolenote) +icon+title+content. La evaluación ES semántica aquí: propintentrestringido a un subset canónico (candidatos:neutral | affirm | risk | threat; validar contravocabularies.md) estampado víafromProp:intent— así eidos tiñe con la MISMA mecánica evaluativa del sistema, no con un enum paralelo inventado. - Eidos: tinte por intent usando roles/escalas (fondo suave + borde +
icono saturado — estudiar cómo tiñe
bannere imitar la mecánica); tipografía del canon;--callout-*para spacing/radius. - Demo: los 4 intents × con/sin título × con contenido multilínea, e
incrustado en un texto largo (anticipo de
prose).
F1.5 prose — contenido largo estilizado
- Gap: contenedor que estiliza HTML/markdown renderizado (h1–h6, p, listas, blockquote, tabla, código, img, hr) con los tokens del tema. Sin esto no hay docs ni blog.
- Membresía: morfo mínimo + eidos (sin soma). El morfo declara UNA part
provider; el trabajo vive en la receta. - Fase 0, comparar: Tailwind Typography (
prose) — el patrón de referencia — · MantineTypographyStylesProvider· Radix Themes. - Norma que lo hace legal: los selectores de elemento descendientes
(
[data-prose] h2,[data-prose] ul…) son selectores estructurales bajo una parte del morfo — sancionados por la norma S1 del eidos-lint (hook = data-attr del morfo o estructural bajo parte; clases NO). - Eidos: LA receta grande del grupo. Reglas: tipografía SOLO vía
primitivos del canon (
--font-*, escala tipográfica; cero literales — y los proporcionales que hagan falta con/* literal: */justificado); medida de lectura--prose-max-width(~65ch) tokenizada;code/prealineados con los tokens decode/code-block(leer sus recetas ANTES; si hay que duplicar valores, es un gap a flag, no un copy-paste); imágenesmax-width:100%; tablas con overflow propio. El HTML interno es del app — aquí la regla B-2 no aplica (es la excepción documentada). - Demo: documento markdown real renderizado (headings, listas anidadas, tabla, código, blockquote, callout incrustado), claro/oscuro, densidades.
F1.6 anchor-nav — índice con scrollspy
- Gap: TOC lateral con sección activa según scroll (docs, settings largos, landing largas).
- Membresía: soma + eidos (observación + estado activo + navegación).
- Fase 0, comparar: AntD
Anchor· MantineTableOfContents· Starlight/Docusaurus TOC. APG: no hay patrón de widget — es unnavlandmark conaria-current; anotarlo en el README (válvula A-1.4 con nota, como pagination/stepper). - Morfo (boceto): parts
provider(nav,aria-labelvíatexts:— aquí SÍ hay string de chrome: "On this page"/"En esta página" → catálogo langs) +list+item+link(¿archetype de item interactivo? — validar contra vocabularies; el link componeLinkdel catálogo). Data:data-activeen item;aria-current. Eventos: click de link = navegación (familia/verbo a decidir en fase 1 contraSEMA_VERBS— candidato familiashift; validar).expression:según criterio D.4. - Soma: registro de secciones objetivo (por id o por
attachPart), observación víadom.observe(IntersectionObserver, umbrales al gusto del provider) — mismas reglas anti-reflow que F1.1. El activo es estado del provider; los effects estampandata-active. Scroll programático al click víadom(scrollIntoView por ActiveDom), no window crudo. - Eidos: raíl vertical con indicador de activo (¡leer la memoria NavMenu Indicator: soma posiciona, eidos da forma, nunca transition de transform!); niveles h2/h3 con indentación tokenizada.
- Demo: página larga real con
prose(F1.5) + anchor-nav vivo.
F1.7 sidebar — navegación vertical de app
✅ E-1 FIRMADA (2026-07-21): la colisión sidebar-app vs árbol-docs (P5/B-5) se resuelve con el componente canónico
nav-tree(F1.8) — este sidebar queda app-céntrico. Además: el dossier (P4) fija el suelo shadcn (23 partes, dos ejes estado/modo, costuras open/onOpenChange/ toggle expuestas desde v1) — la fase 0 dimensiona contra él por E-2.
- Membresía: soma + eidos. Es el componente más pesado de F1 — reservarle tanda propia.
- Fase 0, comparar: shadcn
Sidebar(el patrón de referencia actual) · MantineAppShell.Navbar· Ark/Radix (no lo tienen — anotar). Scope al usuario ANTES de construir: qué features de shadcn entran en v1 (propuesta v1: grupos + colapsable a raíl con tooltips + activo + slots header/footer; FUERA v1: keyboard shortcut global, persistencia — llega del app por D-BLK.6, submenús flotantes en raíl). - Morfo (boceto): parts
provider+header+content+group+group-label+item+footer+trigger(botón colapso — componeButtonconasChild/child pattern). Data:data-collapsed,data-rail,data-active(item). Eventos: toggle de colapso (verbo candidato del set real; evaluación neutral), activación de item si el item es más que unLink— decidir en fase 1 (si item =Linkpuro, la navegación no necesita evento propio del sidebar). - Soma: estado collapsed/rail; grupos colapsables PUEDEN componer el
provider de
collapsible(precedente compose-compound-in-component: NumberField dentro de Knob, con aislamiento de eventos); variante móvil COMPONEDrawer(no lo reimplementa) — el provider decide qué montar por breakpoint responsive del sistema, no matchMedia propio. - Eidos: raíl con
will-changecuidado (memoria:will-change: transformproduce jitter en raíles finos con DPR≠1 — override aauto); tooltips de item en modo raíl componenTooltip; tokens--sidebar-*(width, rail-width, paddings). - Demo: shell de app simulada, toggle colapso, grupos, móvil (375px) con drawer, RTL.
F1.8 nav-tree — árbol de navegación data-driven (E-1, 2026-07-21)
- Gap: navegación jerárquica profunda (docs-shell, árboles de páginas) con active-trail — lo que el dossier (P5) demostró que NO cabe en el sidebar-app: ≥3 niveles, active-trail auto-expandido, auto-colapso de hermanos, profundidad default-open, badges, scroll-active-into-view, presentación móvil. Data-driven por diseño (el app pasa el árbol como datos — precedente sancionado: Menubar; B-5 no se viola porque el componente canónico ES data-driven).
- Membresía — fase 0 OBLIGADA contra nuestro propio catálogo: leer
tree-view(ya existe: selección de nodos) ANTES de diseñar y delimitar la frontera tree-view (selección/widget tree) vs nav-tree (navegación por links, landmark nav) — o justificar extender tree-view. La decisión es del scope-approval. - Fase 0, comparar: sidebars de Starlight/Docusaurus/Fumadocs (dossier
P5 — comportamientos y anatomía) + APG Disclosure Navigation (dossier
P4/P3: botones
aria-expanded/aria-controls, Esc devuelve foco, flechas opcionales) +tree-viewpropio. - Morfo (boceto): parts
provider(nav landmark,aria-labelvíatexts:) +list+item+trigger(grupo colapsable) +link(componeLink;aria-current="page"del MISMO estado quedata-active)- slot badge (compone
Badge). Data:data-active,data-expanded, profundidad como CSS var tokenizada (no attr por nivel). Keyboard: patrón disclosure; roving opcional opt-in.
- slot badge (compone
- Soma: árbol como datos; el activo llega por costura del app (matcher
de URL — el componente NO conoce el router); active-trail expande
ancestros; colapso de grupos evalúa componer
collapsible(compose-first); scroll-active-into-view víadom. - Eidos: indent por nivel con token (
--nav-tree-depth-offset, estilo Mantine pero tokenizado), línea/raíl de nivel, estados active/expanded. - Demo: árbol real de ~40 nodos y 3 niveles (p. ej. el propio mapa de docs), active-trail vivo, móvil 375px.
Cierre F1: los 8 con component:audit --only PASS (o NEEDS-WORK
únicamente por reglas D-* de demos v3 si esa fase global sigue abierta —
anotar en la tabla), morfo:check, eidos-lint por componente, suite
npx vitest run src/uix/eidos verde, npm run check sin regresión sobre
baseline.
F2 — Blocks de sitio (14)
Todos entran por el contrato B. Ficha = Función · Compone · API (partes) · Layout/landmark · v1 · Demo. Regla transversal: fase 0 ligera SIEMPRE (mirar el block equivalente en ≥2 catálogos de referencia de §4 y anotar en el README qué se adopta/descarta). Variantes: v1 = LA variante (una); ampliaciones = Gaps con disposición, no código especulativo.
Depende de: F1.1 (sticky) para F2.1; el resto de F2 no depende de F1.
F2.1 site-header
- Función: cabecera de sitio con afijado y cambio de elevación al pegarse; colapso a menú móvil.
- Compone:
Sticky(F1.1) +Container+NavigationMenu+Button+Drawer(móvil) +Link+Separator. - API:
<SiteHeader>(props:sticky?,container?) +.Brand+.Nav+.Actions+.MobileNav(children del drawer). - Layout/landmark:
<header>+<nav aria-label>; skip-link como primer foco (decidir en su fase 0 si el skip-link vive aquí o en los shells — una sola respuesta, documentada). - v1: brand izquierda · nav centro · actions derecha · drawer móvil;
estilización del estado pegado vía
data-stuck(elevación/fondo con tokens de los componentes compuestos, no CSS nuevo). - Demo: página con scroll largo, claro/oscuro, 375/1280, RTL.
F2.2 hero
- Función: sección de apertura con titular, subtítulo, acciones y media.
- Compone:
Section+Container+Stack/Grid+Heading(nivel configurable, default h1) +Text+Group(acciones conButton) +Badge(chip anuncio) +Image/AspectRatio. - API:
<Hero>(proplayout: 'center' | 'split') +.Eyebrow+.Title+.Description+.Actions+.Media. - Layout/landmark:
<section aria-labelledby={title.id}>; un solo h1 por página es responsabilidad del app — el README lo dice. - v1:
centerysplit(la segunda existe porque discrimina el layout, no por lujo — es el criterio "casos que discriminen"). - Demo: ambos layouts, con/sin media, con badge, dark.
F2.3 feature-grid
- Compone:
Section+Container+AutoGrid+Stack+Icon+Heading+Text. - API:
<FeatureGrid>+.Header(title+description de sección) +.Item(con.ItemIcon/.ItemTitle/.ItemTexto children libres). - v1: items planos (sin Card — la variante card es Gap).
- Demo: 3/6 items, columnas responsive del AutoGrid.
F2.4 pricing
- Compone:
Section+CardGroup/Card+Heading+Text+Badge(plan destacado) +Button+ToggleGroup(mensual/anual) + filas de features (Stack+Group+Iconcheck +Text). - API:
<Pricing>+.Switch(billing toggle; estado de vista local permitido §1) +.Plan(propfeatured?) +.PlanPrice+.PlanFeatures.PlanAction.
- v1: 2–4 planes en fila responsive; el precio mostrado por periodo lo
resuelve el app con el valor del toggle (block emite el cambio vía prop
callback del ToggleGroup — sin formatear moneda: eso es
FormatNumberdel app). - Demo: 3 planes, featured al centro, toggle vivo.
F2.5 testimonials
- Compone:
Section+AutoGrid+Card+Avatar+Text+Group. - API:
<Testimonials>+.Header+.Item(+.ItemAuthorcon Avatar/nombre/cargo). - v1: grid; variante
Carousel= Gap (el componente existe; entra cuando una demo real la pida).
F2.6 faq
- Compone:
Section+Container(medida estrecha) +Heading+Accordion. - API:
<Faq>+.Header+.Item(proxy fino de Accordion.Item con children pregunta/respuesta — respetando el API real del Accordion, que se lee antes). - v1: una columna;
typedel accordion expuesto tal cual.
F2.7 stats-band
- Compone:
Section+Group/AutoGrid+Metrics+CountUp+Text. - API:
<StatsBand>+.Stat(valor + etiqueta;CountUpopt-in por prop). - v1: banda horizontal 2–4 stats; los formatos numéricos llegan ya
formateados o vía
FormatNumbercompuesto por el app.
F2.8 cta
- 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 deForm— leer su README: el block no inventa validación). - Nota: separado de
ctaporque el form introduce a11y y estados (invalid/submitting) que el CTA puro no tiene.
F2.10 site-footer
- Compone:
<footer>+Container+Grid(columnas:Headingsm +StackdeLink) +Separator+Group(IconButtonsociales) +Textlegal. - 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
Bannerexistente +Container+Link+Button(dismiss lo posee Banner si ya lo trae — leer su README). - API:
<SiteBanner>+ children; posición top pareja desite-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(IconButtonsociales 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 = sistemaFormcon handlers del app (misma regla quenewsletter).
F2.14 content-section (E-3)
- Compone:
Section+Container(medida estrecha) +Prose(F1.5) + slots de media (Image/Figurefuera 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 deForm(lo que menos indirection genere gana: no-premature-abstraction). - Regla dura: TODO handler (submit, oauth, resend) llega del app; el
block no conoce transporte ni credenciales. La validación es del sistema
Formcon schemas del app. - Demo: los 4 kinds, estados invalid/submitting, claro/oscuro.
F3.3 data-table
- Función: la tabla de trabajo completa: toolbar (búsqueda + filtros +
visibilidad de columnas + bulk actions con selección) +
Table+Pagination+EmptyStateintegrado. - Compone:
Table+SearchField+DropdownMenu+Button+Pagination+EmptyState(F1.2) +Group/Toolbar. - ⚠️ Fase 0 OBLIGADA y más profunda: leer
soma/components/table+$libs/datagridANTES de diseñar el API — el block debe montar sobre las capacidades reales (sorting/selection/paging que ya existan). Cada capacidad que falte = FLAG con disposición (candidata a canon), nunca lógica de datos dentro del block. El estado de datos es del app; el block orquesta la UI. - API:
<DataTable>+.Toolbar(+.Search/.Filters/.Columns) +.Content(la Table compuesta por el app con su API real) +.Selection(barra bulk) +.Footer(Pagination) +.Empty. - Demo: dataset realista (≥100 filas), búsqueda+filtro+selección+bulk, estado vacío tras filtrar.
F3.4 dashboard
- Compone:
AutoGrid/Grid+Card+Metrics/CountUp+Chart(tipos ya existentes) +Feed+EmptyState. - API:
<Dashboard>+.Stat+.Panel(card con.PanelHeader+ children libres para chart/feed). - v1: composición fija de ejemplo con slots; nada de layout persistible
ni drag (Gap explícito; si un día entra, el drag es de
drag-drop).
F3.5 settings
- Compone:
Container(medida estrecha) +Section+Heading+Separator+ filasField/controles +Callout(F1.4, intentthreat)AlertDialog(confirmación destructiva) +Button.
- API:
<Settings>+.Section(+.SectionTitle/.SectionDescription).Row(label+control+ayuda) +.DangerZone.
- Regla: cada control es el componente del ecosistema (nunca nativos —
B-2); el guardado (por fila o global) lo decide el app vía
Form.
F3.6 user-menu
- Compone:
Avatar+DropdownMenu(+ items conIcon,Separator,Kbdopcional). - API:
<UserMenu>+.Trigger(Avatar + nombre) +.Items(children = items del DropdownMenu real). - Nota D-BLK.6: tema/idioma/logout = items que el APP cablea; el block da la estructura y los puntos de montaje.
F3.7 notifications
- Compone:
Popover(desktop) /Drawer(móvil) +Badge(contador en el triggerIconButton) +Feed+EmptyState+Button(«marcar leídas» — string del app). - API:
<Notifications>+.Trigger+.Panel(+.Header/.List/.Empty/.Footer). - Trampa conocida: panel más ancho que el min-width del Popover →
recorte; pasar
widthpor el prop tokenizado delPopoverContent(la lección ya está aprendida — no re-descubrirla).
F3.8 wizard
- Compone:
Stepper+Form+GroupdeButtons (atrás/siguiente/ finalizar) +Progressopcional. - API:
<Wizard>+.Steps(Stepper compuesto) +.Step(contenido) +.Nav. - Fase 0: leer el API real de
Steppery deForm(validación por paso) — el block solo coordina visibilidad de paso + navegación; el estado de validez lo dictaForm.
F3.9 error-page
- Compone:
Result(F1.3) +GroupdeButton/Link+SearchFieldopcional (404). - API:
<ErrorPage status=…>+ children de acciones. - v1: 404 · 403 · 500 · offline.
F3.10 kanban
- Compone:
DragDrop+Grid/Groupde columnas +Card+VirtualList(columnas largas) +Badge+EmptyStatepor columna. - ⚠️ Fase 0 OBLIGADA: leer
soma/components/drag-dropa fondo; el block mapea sus primitivas reales (zonas, handles, anuncios a11y del provider). Toda carencia = FLAG (candidata a canon), no workaround. - API:
<Kanban>+.Column(+.ColumnHeader) +.Card(children libres). El modelo de datos y la persistencia del orden son del app. - Demo: 3 columnas, drag entre columnas, columna vacía, teclado.
(Diferidos de F3, registrados en F5: scheduler — bloqueado hasta que
chronos aterrice; chat-room — NO va aquí: la familia chat-* es canon y
su roadmap vive 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 (Groupde dosCard/Link). - E-5 firmada: el atajo ⌘K del trigger de búsqueda = listener app-land
documentado en el README del block; el art
shortcutsconserva su disparador F5 (≥2 consumidores reales). - API:
<DocsShell>+.Nav+.Article(prose) +.Toc+.Search.PrevNext.
- v1: 3 columnas desktop → TOC colapsada y sidebar-drawer en móvil.
F4.2 code-showcase
- Compone:
Tabs(Preview/Code) +Surface(lienzo de preview) +CodeBlock+Clipboard+Toolbaropcional. - API:
<CodeShowcase>+.Preview(children vivos) +.Code(children CodeBlock). - v1: preview + código + copiar. (Chips de tema/dirección/densidad del lienzo = Gap, notado — pediría servicios, D-BLK.6.)
F4.3 props-table
- Función: tabla de referencia de un componente generada del morfo —
parts, data-attrs, ARIA, keyboard y eventos salen de
compileMorfo(ventaja estructural única de este framework: el contrato ES introspectable). - Compone:
Table+Code/Kbd+Badge. - API:
<PropsTable morfo={…}>(aquí el input data-driven es legítimo: el "árbol" es el contrato canónico, no contenido inventado). - v1: tablas morfo-backed (parts/attrs/keyboard/eventos con familia·verbo·intent). Tabla de props TS = Gap tracked (requiere extracción de tipos; no improvisar — flag para decidir tooling).
Cierre F4: una página de docs REAL montada con los 3 (un doc del corpus
renderizado en docs-shell con showcase y props-table de un componente
PASS), verificada en navegador.
F5 — Backlog condicionado (no construir sin disparador)
Componentes canon candidatos (cada uno entraría por la ruta de 9 fases):
| Pieza | Disparador |
|---|---|
lightbox (visor imagen zoom/galería) |
cuando un block de media/galería real lo pida |
tour (onboarding spotlight) |
cuando el app-shell tenga consumidor real con onboarding |
hover-card genérico |
cuando un tercer caso no-URL aparezca (hoy link-preview cubre) |
description-list |
primer detail-view real (pareja natural de data-table) |
loading-overlay |
primera pantalla con carga bloqueante real |
transfer-list |
primer admin real con asignación dual |
mention |
ya registrado en next-features.md §7 (chat) — no duplicar aquí |
masonry · bottom-nav · swipe-actions · pull-to-refresh · image-compare · marquee (¿pack?) · watermark · signature-pad |
demanda real; varios son candidatos a pack, no a canon — decidir con la regla de admisión de packs |
art shortcuts (registro global de atajos + cheat-sheet con Kbd) |
cuando ≥2 consumidores reales (command palette global + docs) lo pidan |
Blocks diferidos: scheduler (bloqueado por chronos) · logo-cloud
(bloqueado por decisión marquee) · billing · file-manager ·
profile-card (cuando haya consumidor).
6. Verificación de cierre del plan (definition of done global)
npm run checksin regresión;npm run blocks:checkverde;npm run component:audit→ los 7 de F1 PASS (o NEEDS-WORK solo por D-* de la fase demos global);docs:checkverde.- Las tres páginas de integración (F2 landing · F3 app · F4 docs) renderizan compuestas SOLO de canon+blocks, verificadas visualmente en claro/oscuro, 375/1280 y teclado.
- Doctrina publicada y enlazada (architecture/blocks.md en el mapa; CLAUDE.md al día); cada block con README B-9 completo.
- Ningún gap silenciado: todo ❌/⚠️ de las fases 0 tiene decisión firmada o fila en F5/next-features.
7. Registro
- 2026-07-21 — Plan creado (análisis de ecosistema + decisión de usuario:
tier
blocks). Iniciativa ennext-features.md§8. - 2026-07-21 — F0.1 HECHA: las 7 D-BLK firmadas (repaso en sesión).
D-BLK.1 enmendada frente a la propuesta: el tier vive en
src/uix/blocks/(nosrc/blocks/); referencias del plan actualizadas (tabla de tiers, B-4, blocks-check, F0.3/F0.4). Resto firmado como propuesto. Siguiente: F0.2 (doctrina enarchitecture/blocks.md). - 2026-07-21 — F0 CERRADA (misma sesión): doctrina
docs/architecture/blocks.md· alias$blocks(vite + svelte.config + CLAUDE.md) ·src/uix/blocks/README.md(mapa + template B-9) ·scripts/blocks-check.ts+npm run blocks:check(self-testing) · galeríaweb/routes/blocks/con layout de bootstrap propio (+layout@.svelte, espejo mínimo del de/uix, sin packs sema) · fila E1 endocs/README.md. Nota de commit:docs/README.mdydocs/next-features.mdquedaron FUERA del commit de F0 — ambos traían diff foráneo de otra sesión imposible de separar por staging de archivo completo; sus hunks propios (fila E1 + §8) viajan con el árbol de trabajo hasta que su dueño commitee. Siguiente: F1 (orden recomendado:empty-state→result→callout→sticky→prose→anchor-nav→sidebar). - 2026-07-21 — Estudio de referencia COMPLETO (encargo del usuario:
"a la par y cuanto menos superarlo"): 6 pistas de investigación web en
paralelo →
docs/process/RESEARCH-blocks-references.md(suelos de paridad a nivel de prop por ítem, pitfalls, superación). Regla nueva en §4: toda fase 0 contrasta contra el dossier. Puntero de colisión añadido a F1.7. Enmiendas E-1…E-5 presentadas al usuario (E-1 nav-tree · E-2 filosofía de alcance v1 · E-3 categorías nuevas F2 · E-4 registry/llms/MCP · E-5 ⌘K) — firmas pendientes; se registran aquí. - 2026-07-21 — E-1…E-5 FIRMADAS (todas según recomendación): E-1
componente canónico
nav-treedata-driven (F1.8; F1 pasa de 7 a 8; el naming se valida en su fase 0 como extensión de D-BLK.7); E-2 suelo de paridad = v1 (regla en el intro de F1); E-3 F2 pasa de 10 a 14 (banner · team · contact · content-section; resto de la unión a F5); E-4 distribución registry/llms.txt/MCP registrada como iniciativa propia ennext-features.md§9; E-5 ⌘K = listener app-land (nota en F4.1). Doctrina (architecture/blocks.mdpromotion path) y galería actualizadas. - 2026-07-21 — F1.2
empty-stateHECHA (ruta de 9 fases completa, suelo E-2 del dossier §P3): morfo display 5 partes (scope: ['eidos'], 0 eventos justificados, Titlerole:'heading') + langslabel+ eidos compound (Media kind=icon|mediacon placa 2× glifo ·Title level2–6 default h3, modelo Atlaskit ·Descriptionmeasure 45ch ·Actions label→ role=group, buttonGroupLabel) + recipe sobre el bundle--size-*(título un paso discreto arriba) + demo v2 de 9 tabs + README (Comparativa 5 refs · Passive justification · Gaps con disposición). Verificado:component:auditPASS · eidos-lint 5 morfo-backed + 4 eidos-only sancionados ·svelte-check76E/51W = baseline exacto · navegador claro Y oscuro por estilos computados (título 18→24px, placa 40→64px, chips vivos). Siguiente: F1.3result(comparte esqueleto; fase 0 contra dossier §P3). - 2026-07-21 — F1.3
resultHECHA (ruta completa, suelo E-2 §P3): morfo 6 partes (extrasin archetype — parte genuinamente propia) · status enum de 7 CONwarning(decisión del dossier firme), defaultinfo, HTTP renombrados semánticos y renderizados NEUTROS (código mono grande, jamás rojo — doctrina AntD) · media default compone el mapa doctrinalIntentIcon(fulfill/threat/risk) +Infode catálogo, children lo reemplazan entero (context local eidos-only con getter reactivo) · Title default h2 (vs h3 de empty-state — deliberado) · esqueleto duplicado conscientemente (README lo registra; revisable al 3er consumidor) · sin eje size (paridad AntD). Verificado:component:auditPASS 0E/0W · eidos-lint 6 morfo-backed + 5 eidos-only · guards recipe 37/37 · navegador: success=fulfill verde 84px · error=threat rojo · not-found=«404» mono NEUTRO aria-hidden, copy conmutando, cero errores consola. Siguiente: F1.4callout(decisión IMPORTANT/5º hueco + role=note; dossier §P3). - 2026-07-21 — F1.4
calloutHECHA (ruta completa, suelo E-2 §P3): morfo 4 partes +textscon títulos default localizados por intent (note/tip/warning/caution — el patrón GitHub de label visible; la semántica nunca viaja solo en color) · el hueco IMPORTANT resuelto con el modelo Radix:intent(4, mapea el vocabulario de facto) +coloroverride SOLO bajo neutral (doctrina §4 — intent evaluativo gana), montado sobre la maquinaria C6/THM-2 de badge (forwarders +_palette-*→ 8 roles + 33 escalas + custom con CERO CSS extra; el forward presence-guarded se emitió solo) ·role="note"nombrado por el Title víaaria-labelledbySOLO mientras está montado (registro reactivo por context; bug cazado en navegador: el+=del registro leía el estado dentro del tracking del$effectdel hijo → effect_update_depth y contador a −997 — fixuntrack, lección para la memoria) · Title NO heading (protege el outline de anchor-nav) · icono = mapa doctrinalIntentIcon· escalación tipadarole: status|alert|none(elrole="alert"estático de shadcn NO se copia) · dismissible DIFERIDO a pasada soma+sema (regla de admisión). Verificado: audit PASS 0E/0W · lint 8 morfo-backed/0 invalid · guards 37/37 · navegador: «Nota»/« Atención» localizados, risk=ámbar hue 45-60, IMPORTANT=plum hue 326 por shared layer, labelledby=titleId. Siguiente: F1.1sticky(orden del plan: desbloquea F2; dossier §P3 — offsets, scroll-host, centinela por borde, espejo scroll-state). - 2026-07-21 — F1.1
stickyHECHA (commit998b11d9b, PASS 0E/0W). El primer componente BEHAVIORAL (soma+eidos). Precedido de un fan-out de investigación (workflow, 6 lectores) que fijó el mapa de construcción: template = FeedSentinelProvider; motor =$adom.observeIntersection(nunca scroll+getBoundingClientRect); escritura de attrs porsyncAttrs(nuncadom.applycrudo). Diseño: un StickyProvider registra AMBAS partes (box+sentinel) en un runtime;$effectretorna el observer como teardown; callback async volteastuck=$state; rootMargin DERIVADO del offset (la línea de disparo del centinela = la línea de pin — acoplamiento matemático); centinela = HERMANO de flujo del box (dos nodos raíz), no hijo; offset viaja como--_sticky-offset(dato A8); recipe SOLO posiciona (el consumidor decora[data-sticky][data-stuck]);data-stuck/data-edgeespejan@container scroll-state(stuck: top/bottom)(polyfill del contrato CSS futuro). Verificación clave: 5 tests, incl. 1 real-IntersectionObserver en chromium foreground (no-stuck→scroll→stuck→back, prueba end-to-end de la geometría — lo que el Browser pane suspendido NO puede). Decisión declarada: el test usa elinstallSomaHarnesscompartido (los 86 tests lo hacen) — en tensión con la regla CLAUDE.md «never fake translators»; flaggeada al usuario para ratificar. Notas de commit: nav sidebar fuera (flip CRLF ajeno de 2125 líneas); índices langs/soma reconstruidos sticky-only (entangle conaurasin commitear de sesión paralela). Consola: detectado un warning reactivo residual encallout(registerTitle en $effect) — a investigar. Siguiente: F1.5prose(o F1.6 anchor-nav / F1.8 nav-tree; los 3 restantes que quedan de F1 tras prose son anchor-nav, nav-tree, sidebar). - 2026-07-21 — Review adversarial de sticky (workflow
wrssrn7tx, 3 lentes + verify): 1 hallazgo CONFIRMADO (minor) — pin en eje lógico vs observer físico, diverge en writing-modes verticales. Corregido (6cba9a0b1): recipe atop/bottomfísico + margins físicos; 3 claims «logical/RTL» del demo corregidos. Re-verificado PASS 0E/0W + test real-IO. (1 lente teardown-ssr falló por API stall; caminos cubiertos por tests.) F1.1 sticky CERRADO. - 2026-07-21 — F1.5
proseHECHA (commit5b84749ee, PASS 0E/0W). Quinto F1 (eidos-only). EL DIFERENCIADOR verificado en navegador: reglas de elemento a especificidad CERO (:where([data-prose] EL)) → un<Callout>embebido conserva su tinte/borde/grid AUTOMÁTICAMENTE (su[data-callout]gana a:where), en claro Y oscuro — sinnot-prose, cosa que ninguna ref puede. Escala EM-relativa (un font-size raíz re-deriva todo, no la escala 5× de tailwind); overflow de tabla estilo GitHub; dark gratis por roles (no-invert); propiedades lógicas. Decisión:where(no@scope, diferido a Gaps). Lección: recipe token defs (base.ts) admiten literales sin tripar R-2.7; los literales em en el .css se justifican con/* literal */(21 añadidos). F1 = 5/8. Restan: anchor-nav (F1.6) · nav-tree (F1.8) · sidebar (F1.7). - 2026-07-22 — F1.6
anchor-navHECHA (commitfe2d43b7a, PASS 0E/0W). Sexto F1, segundo BEHAVIORAL (soma+eidos) tras sticky. TOC scrollspy: landmark<nav>+ anchors nativos; detección por IntersectionObserver de banda (rootMargin -{topOffset}px 0px -70% 0px), NUNCA scroll-listener +getBoundingClientRect(doctrina anti-reflow — Mantine/Docusaurus lo violan).aria-current="location"(spec-preciso; solo 1 de 5 refs pone aria-current). Rail por-link con acento endata-active; indent pordata-level. Verificación: test real-IO chromium (anchor-nav-io) — banda + zona muerta al fondo + fallback inicial; el pane suspendido daba ungetComputedStylede color OBSOLETO (falsa alarma, la regla[data-active]sí aplica — elfont-weight:500lo probaba). Review adversarial (4 dimensiones × verificador escéptico, 9/16 confirmadas): arregladas → ref-count en register/unregister (hrefs duplicados / churn ya no tiran un target vivo, +test); transición youtline-offsettokenizados (--duration-fast/--ease-default,calc(--focus-ring-width * -1)). Documentadas como límite v1 (Gaps) → salto instantáneo al fondo (Ctrl+Fin) en zona muerta + orden-enlaces==orden-secciones (ambas piden cambio de contrato de observación v2).data-levelfuera de rango degrada a flush (no arreglado, simplicidad). Además (37e35d7a2):npm run checkglobal destapó 2 errores de tipo en F1.1 sticky (sentinelRef debía serStatenoActive; eidos types importabaStickyPropsen vez del export renombradoProviderProps) — míos, arreglados. Mis archivos suman 0 errores de check (80→73; los 73 son deuda foránea de sesiones paralelas). F1 = 6/8. Restan: nav-tree (F1.8) · sidebar (F1.7). - 2026-07-22 — F1.8
nav-treeEN CURSO (WIP commiteado, sin cerrar). Componente COMPLETO en verde en gates estáticos (audit PASS · eidos-lint 20/0 · svelte-check 0 · contracts mis-partes limpias); morfo+soma+eidos+pack de sema+4 READMEs+todos los registros escritos. Falta demo + verificación en NAVEGADOR + review adversarial + cierre. Data-driven (E-1); disclosure propio (getter de estado + eventosemerge, pack espeja collapsible); filas propias (noLink/Badgecompuestos — Badge diferido a Gap);activeHrefcomo costura. Corregido de paso: README de soma de anchor-nav faltaba (contracts lo destapó). Handoff detallado:docs/process/CONTINUE-nav-tree.md. ⚠️ 2 fallos contracts AJENOS (menubar/radio-group, sesiones paralelas). - 2026-07-22 — F1.8
nav-treeHECHA (cierre de la ruta: demo v2 de 9 tabs con el mapa real de docs —43 nodos, 3 niveles—, verificación en navegador REAL y review adversarial de 5 dimensiones × 3 verificadores escépticos, 22 hallazgos brutos → 10 confirmados, 12 rechazados). Arreglado del review: (1) el emit de sema nunca ocurría — la partegroupes el target de los eventos y se registraba SINref, así queruntime.triggerlanzabaSomaRuntimeTargetErroren silencio (lo cazó el navegador: cerodata-event-*en el grupo frente a los de collapsible); (2) el chevron se comía media fila (inline-size:100%resolvía como flex-basis → 101px de 231 en «Soma»: pulsar el hueco tras la etiqueta plegaba en vez de navegar) → toggle compacto de 24px con suelo de diana WCAG 2.5.8; (3) ciclo potencial enparentByKey(clave duplicada = nodo padre de sí mismo →trailKeysgiraba para siempre y congelaba la pestaña) → clave sufijada + aviso del logger + guarda de ciclo en el paseo; (4) colapso ahora CONTEXTUAL (recuerda elactiveKeybajo el que se hizo: cerrar la sección que lees se respeta, pero caduca al navegar DENTRO — si no, la página actual se quedaba sin fila visible y el trail auto-expandido quedaba anulado; sigue siendo query pura); (5)childya no se comía el árbol (recibechildrenademás deprops— un árbol data-driven no lo puede reautorar el consumidor); (6) nombre accesible del chevron declarado en morfo + localizado («Alternar sección {label}») en vez de duplicar el nombre del enlace; (7) paridad de snippet en la demo + token fantasma--nav-tree-rail-widthfuera del docblock. RTL completo (el glyph se dibuja con bordes lógicos: espeja solo; lo que no espeja es el GIRO → bajo[dir='rtl']las dos rotaciones se intercambian) — el Gap «dirección del chevron en RTL» queda RESUELTO, no diferido. Decisión de usuario (delegada):badgeSÍ entra en v1 (está en el suelo del dossier §P5, los 6 refs lo llevan) resuelto con snippet, no con recursión de eidos — soma declara la partebadgey renderiza el snippet recibido (sin él, el valor crudo: sigue headless), el wrapper de eidos pasa elBadgecanónico; va DENTRO del control de la fila para que su texto entre en el nombre accesible («TSC, New, enlace»).disabledtambién entra, por decisión explícita del usuario tras presentarle el criterio: el nodo conserva la fila pero pierde elhref(no hay nada que activar con Enter, clic ni «abrir en pestaña nueva» — unpointer-events:nonede CSS solo tapa el ratón) y añadearia-disabled+data-disabled; el toggle del grupo NUNCA se deshabilita (disclosure es control de vista, no destino) y un nodo sinhreflo ignora. Verificado:component:auditPASS · eidos-lint 26 morfo-backed / 0 invalid ·svelte-check0 errores en mis archivos ·vitest src/uix/eidos353/353 · navegador real (Playwright, el pane suspendido NO sirve: congela rAF y el scroll-into-view parecía roto) → trail auto-expandido, semaemerge-expand/collapseestampando engroup, teclado nativo (Tab/Enter/ Espacio), foco visible, claro+oscuro, RTL, 375px sin desbordes, 0 errores de consola. F1 = 7/8. Resta: sidebar (F1.7).